前文分析了SqlSession,总的说来就是实现了数据库 *** 作的增删改查方法,但是具体的调用都是通过Executor来实现的,所以分析Executor对了解Mybatis是怎么运行极其重要。
Executor是SqlSession的构造参数之一,他的实例化过程是调用Configuration的newExecutor方法返回的Executor executor = configuration.newExecutor(tx, execType),tx是事务工厂(已经介绍过),execType是执行器类型(SIMPLE、REUSE、BATCH):
- SIMPLE:默认值,会为每个语句创建新的预处理语句
- REUSE:会重用预处理语句
- BATCH:会批量执行所有的更新语句
具体到源码做进一步分析。
newExecutor(tx, execType):
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { //会得到一个默认值 executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } //默认是带缓存的 if (cacheEnabled) { executor = new CachingExecutor(executor); } //如果配置了插件,那么就会用插件进行封装一层 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
所以,默认会返回CachingExecutor对象。
二、CachingExecutor顾名思义就是带有缓存器的执行器,也就是查询会缓存起来,增加、删除、修改时会清除缓存
public CachingExecutor(Executor delegate) { //这里有个委派执行器 this.delegate = delegate; //委派执行器又会包装当前的缓存执行器 delegate.setExecutorWrapper(this); }
我们先分析其query查询方法:
publicList query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //获取具体的sql绑定对象 BoundSql boundSql = ms.getBoundSql(parameterObject); //得到缓存的key值 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); //继续调用查询 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
publicList query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache();//等等缓存对象 if (cache != null) {//如果缓存存在 flushCacheIfRequired(ms);//查看是否需要刷新缓存 if (ms.isUseCache() && resultHandler == null) {//如果设置使用缓存 //如果执行的是存储过程,要确保没有输出参数 ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List list = (List ) tcm.getObject(cache, key); if (list == null) {//如果缓存不存在 //重新查询 list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //设置缓存 tcm.putObject(cache, key, list); // issue #578 and #116 } 返回结果 return list; } } //如果当前缓存不存在,直接查询返回 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
- CacheKey :缓存的主键,分析createCacheKey可知,键值是由查询名称、offset、limit、sql语句、参数值、以及环境id生成。
- TransactionalCacheManager:具体缓存值存放是通过这个来实现的,调用其getObject和putObject方法,底层的存储是利用HashMap来实现,如果执行增删改方法是就会调用其clear方法清除缓存。
说到缓存,就存在一级缓存和二级缓存,一级缓存是SqlSession级别的缓存,只有在同一个session实例下才有效,不同的session不共享缓存,二级缓存是SqlSessionFactory级别,可以不同session共享缓存(mybatis缓存实现将单独分析)。
二、baseExecutor经过上述分析可知,具体的 *** 作又是通过委托对象delegate来实现,也就是具体的SIMPLE、REUSE、BATCH等执行器,具体的执行语句是通过他们的父类baseExecutor实现,如下代码段是其构造方法。
protected baseExecutor(Configuration configuration, Transaction transaction) { //事务对象 this.transaction = transaction; //同步队列 this.deferredLoads = new ConcurrentlinkedQueue<>(); //本地缓存 this.localCache = new PerpetualCache("LocalCache"); //缓存输出参数 this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; //配置对象 this.configuration = configuration; //包装类 this.wrapper = this; }
继续以查询为例分析:
publicList query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } //判断是否需要清除缓存 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List list; try { queryStack++;//加一 //先从缓存中取值 list = resultHandler == null ? (List ) localCache.getObject(key) : null; if (list != null) { //如果缓存命中,处理存储过程中的参数输出 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //否则从数据库中查询 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { //减一 queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // 如果配置的statementType是STATEMENT那么也要清除缓存 clearLocalCache(); } } return list; }
queryFromDatabase:从数据库中查询
privateList queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List list; //在缓存中设置一个占位符 localCache.putObject(key, EXECUTION_PLACEHOLDER); try { //具体的查询 list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } //添加缓存 localCache.putObject(key, list); //如果是存储过程,缓存参数 if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
doQuery:
publicList doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //获取执行的handler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //获取statement对象 stmt = prepareStatement(handler, ms.getStatementLog()); //交给handler执行 return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } }
认真分析,可以发现最后都是交给StatementHandler去执行 *** 作,这个后文再进行分析,本文就不继续展开。
三、SIMPLE、REUSE、BATCH执行器的区别接下来,我们就从源码来分析这三个执行器的区别,SimpleExecutor中就简单的各种do**方法,ReuseExecutor中多了一个Map
ReuseExecutor中的prepareStatement方法。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); if (hasStatementFor(sql)) { stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); putStatement(sql, stmt); } handler.parameterize(stmt); return stmt; }
可以看出不同点在于REUSE会把sql语句和statement对象用HashMap保存起来,下次执行同样的sql语句的时候就会直接重用之前的Statement对象。
我们再看看BatchExecutor对象,这个对象有五个属性值,这个对象会批量的执行更新语句。
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002; private final ListstatementList = new ArrayList<>(); private final List batchResultList = new ArrayList<>(); private String currentSql; private MappedStatement currentStatement;
SIMPLE和RFEUSE的do**方法,就是一个标准的模板方法设计先获取Configuration、再获取StatementHandler对象,然后实例化Statement对象,最后通过handler执行语句。
BatchExecutor中的doUpdate方法:
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException { final Configuration configuration = ms.getConfiguration(); //也是获取statement对象 final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null); final BoundSql boundSql = handler.getBoundSql(); final String sql = boundSql.getSql(); final Statement stmt; //先判断当前执行SQL语句和上一条语句是否一样 if (sql.equals(currentSql) && ms.equals(currentStatement)) { int last = statementList.size() - 1; stmt = statementList.get(last); applyTransactionTimeout(stmt); handler.parameterize(stmt);// fix Issues 322 BatchResult batchResult = batchResultList.get(last); batchResult.addParameterObject(parameterObject); } else { Connection connection = getConnection(ms.getStatementLog()); stmt = handler.prepare(connection, transaction.getTimeout()); handler.parameterize(stmt); // fix Issues 322 currentSql = sql; currentStatement = ms; statementList.add(stmt); batchResultList.add(new BatchResult(ms, sql, parameterObject)); } //这里就是不断的添加需要执行的sql语句 handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; }
我们知道调用addBatch方法后还需要调用executeBatch方法才行,但是这里并没有,所以这个 *** 作就需要在commit方法中去实现,在父类baseExcutor的commit方法会调用一个doFlushStatements方法,BATCH重写了这个方法,这个方法中就调用stmt.executeBatch(),所以这三类执行器的区别也不难理解。
四、总结本文简单介绍了Executor,以及SIMPLE、REUSE、BATCH三类执行器的区别,SqlSession拿到具体的执行语句对象,然后交给Executor来执行,执行器又处理好缓存以及是否重用以及批量更新等 *** 作。然后又把具体的执行工作交给了StatemHandler来执行,接下来我们先分析一下Mybatis的缓存机制,然后再继续往后分析。
以上,有任何不对的地方,请指正,敬请谅解。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)