执行更新语句所需处理的情况较之查询语句要简单不少,两者最大的区别更新语句的执行结果类型单一,处理逻辑要简单不少。除此之外,两者在缓存的处理上也有比较大的区别。更新过程会立即刷新缓存,而查询过程则不会。
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 方法的逻辑并不是很复杂,主要流程如下:
- 获取主键数组(keyProperties)
- 获取 ResultSet 元数据
- 遍历参数列表,为每个主键属性获取 TypeHandler
- 从 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中获取自增主键的值,最后再通过元信息对象将自增主键的值设置到参数中。