执行更新语句所需处理的情况较之查询语句要简单不少,两者最大的区别更新语句的执行结果类型单一,处理逻辑要简单不少。除此之外,两者在缓存的处理上也有比较大的区别。更新过程会立即刷新缓存,而查询过程则不会。

execute

我们还是从 MapperMethod 的 execute 方法开始看起

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// -☆- MapperMethod
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: { // 执行插入语句
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(
sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: { // 执行更新语句
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(
sqlSession.update(command.getName(), param));
break;
}
case DELETE: { // 执行删除语句
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(
sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
// ...
break;
case FLUSH:
// ...
break;
default:
throw new BindingException("……");
}
if (result == null && method.getReturnType().isPrimitive() &&
!method.returnsVoid()) {...}
return result;
}

插入、更新以及删除操作最终都调用了SqlSession接口中的方法。这三个方法返回值均是受影响行数,是一个整型值。rowCountResult方法负责处理这个整型值,该方法的逻辑暂时先不分析,放在最后分析。下面分析 SqlSession 的实现类 DefaultSqlSession 的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// -☆- DefaultSqlSession
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
public int delete(String statement, Object parameter) {
return update(statement, parameter);
}
public int update(String statement, Object parameter) {
try {
dirty = true;
// 获取 MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用 Executor 的 update 方法
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("……");
} finally {
ErrorContext.instance().reset();
}
}

insert 和 delete 方法最终都调用了同一个 update 方法,这就是为什么我把他们归

为一类的原因。既然它们最终调用的都是同一个方法

MyBatis 为什么还要在 SqlSession中提供这么多方法呢,难道只提供 update 方法不行么?

答案是:只提供一个update方法从实现上来说完全可行,但是从接口的语义化的角度来说,这样做并不好。一般情况下,使用者觉得 update 接口方法应该仅负责执行 UPDATE 语句,如果它还兼职执行其他的SQL语句,会让使用者产生疑惑。对于对外的接口,接口功能越单一,语义越清晰越好。

Executor 的 update 方法。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// -☆- CachingExecutor
public int update(MappedStatement ms, Object parameterObject)
throws SQLException {
// 刷新二级缓存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}

// -☆- BaseExecutor
public int update(MappedStatement ms, Object parameter)
throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 刷新一级缓存
clearLocalCache();
return doUpdate(ms, parameter);
}

Executor 实现类中的方法在进行下一步操作之前,都会先刷新各自的缓存。默认情况下,

insert、update 和 delete 操作都会清空一二级缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// -☆- SimpleExecutor
public int doUpdate(MappedStatement ms, Object parameter)
throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建 StatementHandler
StatementHandler handler = configuration.newStatementHandler(
this, ms, parameter, RowBounds.DEFAULT, null, null);
// 创建 Statement
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用 StatementHandler 的 update 方法
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}

PreparedStatementHandler 的 update 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// -☆- PreparedStatementHandler
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行 SQL
ps.execute();
// 返回受影响行数
int rows = ps.getUpdateCount();
// 获取用户传入的参数值,参数值类型可能是普通的实体类,也可能是 Map
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
// 获取自增主键的值,并将值填入到参数对象中
keyGenerator.processAfter(executor,mappedStatement,ps,parameterObject);
return rows;
}

KeyGenerator

对于 insert 语句,有时候我们还想获取自增主键的值,因此我们需要进行一些额外的操作。这些额外操作的逻辑封装在KeyGenerator的实现

KeyGenerator 是一个接口,目前它有三个实现类,分别如下:

  • Jdbc3KeyGenerator

    • 用于获取插入数据后的自增主键数值
  • SelectKeyGenerator

    • 某些数据库不支持自增主键,需要手动填写主键字段,此时需要借助 SelectKeyGenerator 获取主键值
  • NoKeyGenerator

    • 一个空实现

Jdbc3KeyGenerator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// -☆- Jdbc3KeyGenerator
public void processBefore(Executor executor, MappedStatement ms,
Statement stmt, Object parameter) {
// 空方法
}

public void processAfter(Executor executor, MappedStatement ms,
Statement stmt, Object parameter) {
processBatch(ms, stmt, getParameters(parameter));
}

public void processBatch(MappedStatement ms, Statement stmt,
Collection<Object> parameters) {
ResultSet rs = null;
try {
rs = stmt.getGeneratedKeys();
final Configuration configuration = ms.getConfiguration();
final TypeHandlerRegistry typeHandlerRegistry =
configuration.getTypeHandlerRegistry();
// 获取主键字段
final String[] keyProperties = ms.getKeyProperties();
// 获取结果集 ResultSet 的元数据
final ResultSetMetaData rsmd = rs.getMetaData();
TypeHandler<?>[] typeHandlers = null;
// ResultSet 中数据的列数要大于等于主键的数量
if (keyProperties != null &&
rsmd.getColumnCount() >= keyProperties.length) {
// 遍历 parameters
for (Object parameter : parameters) {
// 对于批量插入,ResultSet 会返回多行数据
if (!rs.next()) {
break;
}
final MetaObject metaParam =
configuration.newMetaObject(parameter);
if (typeHandlers == null) {
// 为每个主键属性获取 TypeHandler
typeHandlers = getTypeHandlers(typeHandlerRegistry,
metaParam, keyProperties, rsmd);
}
// 填充结果到运行时参数中
populateKeys(rs, metaParam, keyProperties, typeHandlers);
}
}
} catch (Exception e) {
throw new ExecutorException(...);
} finally {...}
}

private Collection<Object> getParameters(Object parameter) {
Collection<Object> parameters = null;
if (parameter instanceof Collection) {
parameters = (Collection) parameter;
} else if (parameter instanceof Map) {
Map parameterMap = (Map) parameter;
// 如果 parameter 是 Map 类型,则从其中ᨀ取指定 key 对应的值。
// 至于 Map 中为什么会出现 collection/list/array 等键。大家
// 可以参考 DefaultSqlSession 的 wrapCollection 方法
if (parameterMap.containsKey("collection")) {
parameters = (Collection) parameterMap.get("collection");
} else if (parameterMap.containsKey("list")) {
parameters = (List) parameterMap.get("list");
} else if (parameterMap.containsKey("array")) {
parameters = Arrays.asList((Object[])parameterMap.get("array"));
}
}
if (parameters == null) {
parameters = new ArrayList<Object>();
// 将普通的对象添加到 parameters 中
parameters.add(parameter);
}
return parameters;
}

Jdbc3KeyGenerator 的 processBefore方法是一个空方法,processAfter则是一个空壳方法,只有一行代码。Jdbc3KeyGenerator的重点在processBatch方法中,由于存在批量插入的情况,所以该方法的名字类包含 batch单词,表示可处理批量插入的结果集。processBatch 方法的逻辑并不是很复杂,主要流程如下:

  1. 获取主键数组(keyProperties)
  2. 获取 ResultSet 元数据
  3. 遍历参数列表,为每个主键属性获取 TypeHandler
  4. 从 ResultSet 中获取主键数据,并填充到参数中

第 4 个步骤需要分析一下。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void populateKeys(ResultSet rs, MetaObject metaParam, 
String[] keyProperties, TypeHandler<?>[] typeHandlers)
throws SQLException {
// 遍历 keyProperties
for (int i = 0; i < keyProperties.length; i++) {
// 获取主键属性
String property = keyProperties[i];
TypeHandler<?> th = typeHandlers[i];
if (th != null) {
// 从 ResultSet 中获取某列的值
Object value = th.getResult(rs, i + 1);
// 设置结果值到运行时参数中
metaParam.setValue(property, value);
}
}
}

populateKeys 方法首先是遍历主键数组,然后通过TypeHandler从ResultSet中获取自增主键的值,最后再通过元信息对象将自增主键的值设置到参数中。