【Mybatis】【SQL执行过程】【四】Mybatis源码解析-Insert的执行过程
1 前言
上节带大家简单回顾了下 SqlSession以及内部的执行器的创建,那么这节我们就开始看我们的语句都是如何执行的。
调试代码:
// xml <insert id="insertOne" parameterType="org.apache.ibatis.test.po.DemoPo" useGeneratedKeys="true" keyProperty="id"> insert into ${params.tableName}(name) values (#{params.name}); </insert> // mapper void insertOne(@Param("params")DemoPo po);
还记得我们的出发点应该在那里么,就是我们的 Mapper接口的JDK代理增强 MapperProxy里的 invoke,会创建出对应的 MapperMethod 对象然后执行 execute,我们就从这里开始看起,看 insert 都具体做了哪些。
2 源码分析
2.1 通读
那么进入到 SqlSession中来看下具体的执行过程:
/** * DefaultSqlSession * statement 是什么? 也就是 Mapper全类名+方法名 * parameter 就是解析后的参数项以及值 */ public int insert(String statement, Object parameter) { // 可以看到 insert 其实也是 update return update(statement, parameter); } @Override public int update(String statement, Object parameter) { try { // dirty 污染,增删改都会置为 true,select 不会 dirty = true; // 获取到语句对应的 MappedStatement 对象 MappedStatement ms = configuration.getMappedStatement(statement); // 调用执行器进行执行 return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
然后我们进入执行器的执行,我们默认的执行器是 SImplExecutor,并且默认是开启了二级缓存,所以我们的执行器是包装了 SimpleExecutor 的 CacheExecutor,那我们进入到 CacheExecutor 来看一下:
// CachingExecutor public int update(MappedStatement ms, Object parameterObject) throws SQLException { /** * 创建 MappedStatement 对象的时候的属性 flushCacheRequired 查询false 增删改都是true * MapperBuilderAssistant 中 flushCacheRequired(valueOrDefault(flushCache, !isSelect)) * 这里清的是事务缓存 TransactionalCacheManager负责管理,里边维护着 Map<Cache, TransactionalCache> * Map<Cache, TransactionalCache> key->cache 就是 mapper 里的<cache/>标签 value->TransactionalCache * TransactionalCache:Map<Object, Object> entriesToAddOnCommit; * 事务缓存,解决用来解决脏读的(后续单独讲哈) */ flushCacheIfRequired(ms); // BaseExecutor 执行器去执行 return delegate.update(ms, parameterObject); }
我们会看到有一个方法是 flushCacheIfRequired,用于清理事务缓存的,我们后边会单独讲哈,这里我们先看大体的执行过程哈,我们继续:
// BaseExecutor public int update(MappedStatement ms, Object parameter) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } // 更新操作会清掉一级缓存 clearLocalCache(); // 执行更新 return doUpdate(ms, parameter); }
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); /** * 创建的都是 RoutingStatementHandler 只不过里边的 StatementHandler 不一样,操作也是调用对用的 handler 去执行 * 根据 MappedStatement 类型创建不同的 handler 每种 handler的实例化都会调用父类 BaseStatementHandler 进行实例化 * STATEMENT -> SimpleStatementHandler * PREPARED -> PreparedStatementHandler **默认 * CALLABLE -> CallableStatementHandler * 创建 MappedStatement 默认的:StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())) * * 还会涉及插件哈 插件以后单独说哈 */ StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); /** * 一些准备工作 */ stmt = prepareStatement(handler, ms.getStatementLog()); // 执行更新 return handler.update(stmt); } finally { closeStatement(stmt); } }
到这里我们看到执行器的执行过程大致是:
- 创建 StatementHandler
- 执行前的准备工作
- 执行
那么我们来看下三者的具体执行操作。
2.2 创建 StatementHandler
我们来看下 StatementHandler 的创建过程:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { // 创建 StatementHandler StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); // 插件加上 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } }
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql); }
可以看到创建的 StatementHander 都是 RoutingStatementHandler,类型的只不过里边持有的 handler 会根据语句的类型来创建,默认的就是 PreparedStatementHandler,该 handler 的实例化又会调用父类的实例化,我们来看下这几个类的类图,方便理解:
其实跟执行器的类图结构基本一摸一样,那么我们来看下实例化内都做了什么:
// BaseStatementHandler protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { this.configuration = mappedStatement.getConfiguration(); this.executor = executor; this.mappedStatement = mappedStatement; // 分页参数 this.rowBounds = rowBounds; // 类型转换器 this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); // 对象工厂用于结果操作 this.objectFactory = configuration.getObjectFactory(); // 初始化 sql 语句 if (boundSql == null) { // issue #435, get the key before calculating the statement generateKeys(parameterObject); boundSql = mappedStatement.getBoundSql(parameterObject); } this.boundSql = boundSql; this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql); this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql); }
里边有一个很重要的 boundSql 这个就是我们的 sql 语句,这里需要进行创建,那么我们来看下。
2.2.1 generateKeys
这个是什么呢,我们写 insert 的时候是不是有的时候会获取自增的主键,这个就是辅助我们的,我们看下:
protected void generateKeys(Object parameter) { // 获取到主键的生成器 KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); ErrorContext.instance().store(); keyGenerator.processBefore(executor, mappedStatement, null, parameter); ErrorContext.instance().recall(); }
那么主键的生成器是怎么来的呢,就是在解析的时候(解析的时候都具体讲过),这里我们简单来看下:
// 解析 <selectKey> 节点 insert 和 update 有这个 processSelectKeyNodes(id, parameterTypeClass, langDriver); // 获取 KeyGenerator 实例 if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { // 判断有没有开启 useGeneratedKeys 属性,有的话就会赋值Jdbc3KeyGenerator生成器,否则就是 NoKeyGenerator keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; }
主键生成器的类型大致有三种:
- JDBC3KeyGentor 这个是自增型主键获取 before什么也不做,after 会根据 JDBC返回的结果解析主键值并设置回给你
- NoKeyGenerator 不做任何操作
- SelectKeyGenerator 就是我们在 insert 中配置的 SelectKey标签,会被解析成这种类型的生成器
主键生成器有 before 和 after两个时机,也就是这个主键是要在插入动作前设置还是在插入动作后设置,这里如果我们配置了插入前获取,就会通过 SelectKeyGenerator 获取主键值,并设置进你的参数项中。
2.2.2 BoundSql 的创建
语句解析以及赋值,这个比较复杂我们后边会单独的讲,这里我们看下结果:
2.3 执行前的准备工作
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 获取连接 Connection connection = getConnection(statementLog); // 统一准备工作 超时时间的设置等 stmt = handler.prepare(connection, transaction.getTimeout()); // 各自的特定化准备工作,就是每种 StatementHandler 各自的特性话工作 比如这里就会对我们的占位符进行设置 #{},就不进去看了哈 handler.parameterize(stmt); return stmt; }
2.4 执行
/** * PreparedStatementHandler * 这里就会 回归本质了 跟我们以前自己写 JDBC一样的 */ public int update(Statement statement) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 执行 ps.execute(); // 获取更新行数 int rows = ps.getUpdateCount(); Object parameterObject = boundSql.getParameterObject(); // 主键生成器 主键赋值 KeyGenerator keyGenerator = mappedStatement.getKeyGenerator(); keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject); return rows; }
3 小结
本节我们大致了解了 insert 的一个执行过程,有理解不对的地方欢迎指正哈。