咱们在上一章介绍到,Mybatis会将全部数据库操做转换成iBatis编程模型,经过门面类SqlSession来操做数据库,可是咱们深刻SqlSession源码咱们会发现,SqlSession啥都没干,它将数据库操做都委托给你了Excuter,如图:java
在BaseExecutor定义了Executor的基本实现,如查询一级缓存,事务处理等不变的部分,操做数据库等变化部分由子类实现,使用了模板设计模式,下面咱们来看下查询方法的源码:git
@Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } /** * 全部的查询操做最后都是由该方法来处理的 */ @SuppressWarnings("unchecked") @Override public <E> List<E> 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<E> list; try { // 查询层次加一 queryStack++; // 查询一级缓存 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { // 处理存储过程的OUT参数 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) { // issue #482 clearLocalCache(); } } return list; } private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> 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; }
queryFromDatabase() 方法中,咱们能够看到doQuery使用的是模板方法,具体逻辑是由子类来实现的,这样作的好处是,子类只关心程序变化的部分,其余不变的部分由父类实现。提升了代码的复用性和代码的扩展性。github
普通的执行器,Mybatis的默认使用该执行器,每次新建Statement。咱们仍是来看下查询方法的源码:sql
@Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { // 获取Mybatis配置类 Configuration configuration = ms.getConfiguration(); // 根据配置类获取StatementHandler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 建立Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; // 获取Connection链接 Connection connection = getConnection(statementLog); // 根据Connection获取Statement stmt = handler.prepare(connection, transaction.getTimeout()); // 设置参数 handler.parameterize(stmt); return stmt; }
经过
stmt = handler.prepare(connection, transaction.getTimeout());
方法咱们能够看出每次是新建Statement
。数据库
能够重用的执行器,复用的是Statement,内部以sql语句为key使用一个Map将Statement对象缓存起来,只要链接不断开,那么Statement就能够重用。编程
由于每个新的SqlSession都有一个新的Executor对象,因此咱们缓存在ReuseExecutor上的Statement的做用域是同一个SqlSession,因此其实这个缓存用处其实并不大。咱们直接看下获取Statement
源码,其余部分和SimpleExecutor
查询方法同样。设计模式
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql(); if (hasStatementFor(sql)) { // 获取复用的Statement stmt = getStatement(sql); applyTransactionTimeout(stmt); } else { // 新建Statement,并缓存 Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); putStatement(sql, stmt); } handler.parameterize(stmt); return stmt; } private boolean hasStatementFor(String sql) { try { // 根据sql判断是否缓存了Statement,并判断Connection是否关闭 return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed(); } catch (SQLException e) { return false; } } private Statement getStatement(String s) { return statementMap.get(s); } private void putStatement(String sql, Statement stmt) { statementMap.put(sql, stmt); }
批处理执行器,经过封装jdbc的 statement.addBatch(String sql) 以及 statement.executeBatch(); 来实现的批处理。该执行器的事务只能是手动提交模式。缓存
咱们平时执行批量的处理是通常还可使用sql拼接的方式。mybatis
执行批量更新时建议一次不要更新太多数据,若是更新数据量比较大时能够分段执行。app
若是开启了二级缓存那么Mybatis会使用CachingExecutor
执行器,CachingExecutor
使用了装饰器模式。
@Override public <E> List<E> 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<E> list = (List<E>) 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); }
经过源码咱们发现,整个查询流程变成 了 L2 -> L1 -> DB。CachingExecutor
的查询流程,增长了二级缓存的查询操做。
咱们在实际使用缓存过程当中通常不多使用Mybatis的二级缓存,若是想作二级缓存,建议直接在service层面使用第三方缓存框架,推荐使用为监控而生的多级缓存框架 layering-cache,使用更方便灵活,查询流程是 L1 -> L2 -> DB。