mybatis——Executor
映射器了解完了,sqlSession.select()实际上是Executor.query(),所以sql执行实际是executor执行的。
一、BaseExecutor——实际的执行器(包含一级缓存实现)
BaseExecutor是一个Executor的顶级抽象类,实现了Executor接口,定义sql执行的执行器。
模板设计模式的运用,将一部分方法逻辑延迟给子类来实现。(专业术语:钩子)
BaseExecutor有4个实现子类:
下面介绍下BaseExecutor的实现
1、变量与构造方法
/* org.apache.ibatis.executor.BaseExecutor */ protected Transaction transaction;//事务 protected Executor wrapper;//一个指向自己(this)的指针 protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads; //mybatis的一级缓存 protected PerpetualCache localCache; protected PerpetualCache localOutputParameterCache; //configuration对象 protected Configuration configuration; protected int queryStack; //关闭状态哨兵 private boolean closed; protected BaseExecutor(Configuration configuration, Transaction transaction) { this.transaction = transaction; this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>(); this.localCache = new PerpetualCache("LocalCache"); this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache"); this.closed = false; this.configuration = configuration; this.wrapper = this; }
2、主要方法
由于没有注释,所以方法作用不是很清楚,简单分了下类
/* org.apache.ibatis.executor.Executor */ ResultHandler NO_RESULT_HANDLER = null; /* * sql执行方法CRUD */ int update(MappedStatement ms, Object parameter) throws SQLException; <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException; /* * BatchExecutor:执行所有的statement并关闭
* ReuseExecutor:关闭所有复用的statemet */ List<BatchResult> flushStatements() throws SQLException; /* * 事务相关 */ void commit(boolean required) throws SQLException; void rollback(boolean required) throws SQLException; Transaction getTransaction(); /* * 缓存相关 */ CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql); boolean isCached(MappedStatement ms, CacheKey key); void clearLocalCache(); void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType); /* * 自身状态相关 */ void close(boolean forceRollback); boolean isClosed(); void setExecutorWrapper(Executor executor);
① sql方法执行相关
方法过多,选取一个doQuery执行ff
/* 方法过多,选取一个方法研究 */ /* org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler) */ public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //获取sql BoundSql boundSql = ms.getBoundSql(parameter); //创建缓存key CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //sql校验是否存在 ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId()); //关闭哨兵,如果executor已关闭,抛出异常 if (closed) { throw new ExecutorException("Executor was closed."); } //清除缓存(mybatis一级缓存) //queryStack == 0 :正在执行的select类型的sql的个数 //并且 mappedStatement的flushCacheRequired属性为true,对应前面<sql>中的flushCache属性默认:<select> : false ; 非<select> true if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; //根据cacheKey获取缓存 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { //缓存存在,由于默认preparedStatement,这里相当于空方法 handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //数据库查询 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { //延迟加载,什么时候需要延迟加载 //关联映射时,select返回的resultMap中嵌套了子select查询,延迟加载将select与嵌套的子select查询的结果resultMap关联起来。 //具体例子 订单查询的结果中嵌套订单详情查询。 for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 // 一级缓存的作用域是statement级别,清除缓存;默认的是session会话级别,不清楚缓存 clearLocalCache(); } } return list; }
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql):数据库查询
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); } //放入缓存到baseExecutor.localCache中 localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; } protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
查看了下BaseExecutor类,钩子方法只有4个,都是与sql执行相关的方法。
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException; protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException; protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException; protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException;
这里看看各个子类是怎样执行的,还是以doQuery()方法为研究点
SimpleExecutor(默认的BaseExecutor):每创建一个statement,执行完sql后,关闭statement
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { //获取configuration对象 Configuration configuration = ms.getConfiguration(); //应用configuration的设置--<settings>标签,驼峰命名等 //另外还会执行拦截器 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); //sql执行 statement.execute() //查询结果处理resultHandler.handleResultSet();result-->resultMap(处理嵌套sql) return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; //获取连接 Connection connection = getConnection(statementLog); //创建statement,并设置sql,默认是PreparedStatement stmt = handler.prepare(connection, transaction.getTimeout()); //设置参数parameters handler.parameterize(stmt); return stmt; } /* org.apache.ibatis.session.Configuration#newStatementHandler */ public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { //应用configuration的一些设置 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); //拦截器执行 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; }
ReuseExecutor:创建一个statement,放入到一个statementMap<sql,statement>中实现statement复用,会设置一个事务超时时间,防止事务一直开启
statement关闭时机:调用doFlushStatement()时,
调用doFlushStatement()时机:① 显式调用:sqlSession.flushStatement(); ② 事务的提交、回退 sqlSession.commit();
/* org.apache.ibatis.executor.ReuseExecutor#doQuery */ public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } 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 { Connection connection = getConnection(statementLog); stmt = handler.prepare(connection, transaction.getTimeout()); putStatement(sql, stmt); } handler.parameterize(stmt); return stmt; } /* * doFlushStatments关闭所有的statement * 执行时机:显式调用:sqlSession.flushStatements() */ public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { for (Statement stmt : statementMap.values()) { closeStatement(stmt); } statementMap.clear(); return Collections.emptyList(); }
BatchExecutor:doQuery()创建一个statement,执行完sql,关闭statement,与SimpleExecutor不同主要在于doUpdate方法:
BatchExecutor的doUpdate方法不是立即执行,而是将各种statement放入到一个list中,并且一个statement也可能存储多个sql(执行sql时必须连续,不连续,会生成两个statement)
然后在调用doFlushStatement()时执行所有的statement并且关闭statement:
调用doFlushStatement的时机:① 显式调用:sqlSession.flushStatement() ② 每次执行查询之前。③ 事务提交,sqlSession.commit()
/* org.apache.ibatis.executor.SimpleExecutor#doUpdate 与doQuery没有区别 */ public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); } finally { closeStatement(stmt); } } /* org.apache.ibatis.executor.BatchExecutor#doUpdate */ public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException { final Configuration configuration = ms.getConfiguration(); 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; if (sql.equals(currentSql) && ms.equals(currentStatement)) { //当执行sql,sql与当前sql相同,并且mappedStatement也相同 //刷新preparedStatement的事务时间 //追加parameter到preparedStatement中 //追加resultMap到batchResult中 //即同一个prepareStatement中放了多个sql,这些sql仅参数不同 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 { //初次执行sql时与当前sql或mappedStatement不同时 //创建connect连接 //创建statement并设置有效时间 //记录当前sql、mappedStatement //记录添加statement到statementList中 //记录添加resultMap到batchResultList中 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放入list实现的队列中(FIFO) //并没有执行,执行doFlushStatements()方法时才会执行 // handler.parameterize(stmt); handler.batch(stmt); return BATCH_UPDATE_RETURN_VALUE; } /* 执行doFlushStatements的时机:比ReuseExecutor多一个。 * 显式调用:sqlSession.flushStatements() * 执行doQuery时会先执行doFlushStatements()后查询
* 事务提交:sqlSession.commit(); */ public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException { try { List<BatchResult> results = new ArrayList<BatchResult>(); if (isRollback) { return Collections.emptyList(); } for (int i = 0, n = statementList.size(); i < n; i++) { Statement stmt = statementList.get(i); applyTransactionTimeout(stmt); BatchResult batchResult = batchResultList.get(i); try { batchResult.setUpdateCounts(stmt.executeBatch()); MappedStatement ms = batchResult.getMappedStatement(); List<Object> parameterObjects = batchResult.getParameterObjects(); KeyGenerator keyGenerator = ms.getKeyGenerator(); if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) { Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator; jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects); } else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141 for (Object parameter : parameterObjects) { keyGenerator.processAfter(this, ms, stmt, parameter); } } // Close statement to close cursor #1109 closeStatement(stmt); } catch (BatchUpdateException e) { StringBuilder message = new StringBuilder(); message.append(batchResult.getMappedStatement().getId()) .append(" (batch index #") .append(i + 1) .append(")") .append(" failed."); if (i > 0) { message.append(" ") .append(i) .append(" prior sub executor(s) completed successfully, but will be rolled back."); } throw new BatchExecutorException(message.toString(), e, results, batchResult); } results.add(batchResult); } return results; } finally { for (Statement stmt : statementList) { closeStatement(stmt); } currentSql = null; statementList.clear(); batchResultList.clear(); } }
ResultLoaderMap.ClosedExecutor:ResultLoaderMap的一个私有内部类,仅供ResultLoaderMap对象使用,不支持sql操作,仅close状态控制。
protected <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { throw new UnsupportedOperationException("Not supported."); }
② 事务相关
//开启事务 public Transaction getTransaction() { if (closed) { throw new ExecutorException("Executor was closed."); } return transaction; } //提交事务 public void commit(boolean required) throws SQLException { if (closed) { throw new ExecutorException("Cannot commit, transaction is already closed"); } //清空缓存 clearLocalCache(); //清空statement,BatchExecutor会清空,清空前先执行 flushStatements(); if (required) { transaction.commit(); } } //回退 public void rollback(boolean required) throws SQLException { if (!closed) { try { //清空缓存 clearLocalCache(); //清空statement,BatchExecutor不会清空。 flushStatements(true); } finally { if (required) { transaction.rollback(); } } } }
③ 缓存相关
/* org.apache.ibatis.executor.BaseExecutor */ //生成缓存key,
//用sql、parameters、environmentId、mappedStatementId、offset+limit生成一个hashCode public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { if (closed) { throw new ExecutorException("Executor was closed."); } CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); // mimic DefaultParameterHandler logic for (ParameterMapping parameterMapping : parameterMappings) { if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } cacheKey.update(value); } } if (configuration.getEnvironment() != null) { // issue #176 cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; } //判断sql是否有缓存 public boolean isCached(MappedStatement ms, CacheKey key) { return localCache.getObject(key) != null; } //清空缓存(mybatis一级缓存) public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } }
④ 关闭
public void close(boolean forceRollback) { try { try { rollback(forceRollback); } finally { if (transaction != null) { transaction.close(); } } } catch (SQLException e) { // Ignore. There's nothing that can be done at this point. log.warn("Unexpected exception on closing transaction. Cause: " + e); } finally { transaction = null; deferredLoads = null; localCache = null; localOutputParameterCache = null; closed = true; } } public boolean isClosed() { return closed; } public void setExecutorWrapper(Executor wrapper) { this.wrapper = wrapper; }
3、总结
① Executor接口实现功能包括四个模块:sql执行模块、事务模块、缓存模块、状态模块(关闭状态)
② 4个功能的具体实现都在BaseExecutor抽象类中,注意以下说的缓存就是mybatis的一级缓存。
- sql执行模块:先查询缓存,无缓存后执行sql,执行完成后,将结果放入到缓存中,具体执行sql的方法是钩子方法,延迟到子类中。
- 事务模块:开启事务,事务提交,事务回退。需要注意的是事务提交和事务回退会清空一级缓存,还会清空statement
- 缓存模块:生成cacheKey,判断是否存在缓存,清空缓存,chachKey的hashCode是多个属性联合生成的,包括id、sql、parameters、offset+limit、environmentId。
- 状态模块:关闭Executor,清空所有资源;判断关闭状态。
③ 执行sql的具体实现延迟到了4个子类中,注意statement创建之前,会应用configuration对象的设置<setting>,及执行拦截器方法<plugin>中
- SimpleExecutor:每次执行sql时,创建一个statement,执行sql,关闭statement
- ReuseExecutor:初次执行sql时,创建一个statement,后续此sql的执行都是用此statement执行,调用doFlushStatement会清空所有statement。(statement复用)
- BatchExecutor :执行update的sql时,创建一个statement放入到一个队列中,不立即执行,另外如果执行的sql与队尾的statement相同,将sql放入到statement中,注意是队尾statement。在调用doFlushStatement()时才会执行statement。执行query的sql时,先执行doFlushStatement(),然后与SimpleExecutor一致
- closeExecutor:一个无用的Executor
④ doFlushStatement的时机:
- SimpleExecutor:由于statement创建执行后直接关闭了,所以doFlushStatement方法无用。
- ReuseExecutor:显式调用sqlSession.flushStatement;事务提交与回退;Executor关闭
- BatchExecutor:显式调用sqlSession.flushStatement;事务提交;Executor关闭;每次查询sql执行之前
⑤ 关联映射时,延迟加载的使用。
二、CachingExecutor——二级缓存执行器
缓存执行器:mybatis二级缓存的实现,下面是CachingExecutor简单结构图
CachingExecutor声明了一个Executor对象和一个缓存管理对象TransactionCacheManager。
CachingExecutor执行sql时,会先使用先校验二级缓存,后使用BaseExecutor(内含一级缓存)执行sql。
实际使用时:开启一次会话sqlSession时,创建的Executor是new CachingExecutor(new SimpleExecutor);
1、变量与构造方法
private final Executor delegate; private final TransactionalCacheManager tcm = new TransactionalCacheManager(); public CachingExecutor(Executor delegate) { this.delegate = delegate; delegate.setExecutorWrapper(this); }
2、方法实现
CachingExecutor的大部分方法都使用策略模式调用内部变量executor的方法。选取几个重要的方法。
①缓存相关方法的实现:
最终调用的都是BaseExecutor的方法
意味着:一级缓存与二级缓存的cacheKey相同;CachingExecutor.clearLocalCache()清理的是一级缓存
/* org.apache.ibatis.executor.CachingExecutor#createCacheKey */ public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { //BaseExecutor.createCacheKey() return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql); } //调用baseExecutor.isCached public boolean isCached(MappedStatement ms, CacheKey key) { return delegate.isCached(ms, key); } //调用baseExecutor.clearLocalCache():注意这里是清空一级缓存 public void clearLocalCache() { delegate.clearLocalCache(); }
② 查询方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //跟一级缓存相比,生成的boundSql一致,生成的cacheKey一致 BoundSql boundSql = ms.getBoundSql(parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //获取二级缓存,二级缓存对应mappedStatement.cache
//前面<mapper>标签解析时漏掉了,<mapper>里有<cache>标签
//在解析<cache>标签时会创建一个cache对象放入到configuration.caches中同时会关联到mapperStatement.cache
//二级缓存有多个cache实例,一个mapper.xml对应一个cache实例 Cache cache = ms.getCache(); if (cache != null) { //mappedStatement.flushCacheRequired == true ,删除缓存mappedStatement.cache.clear() //复习下mappedStatement.flushCacheRequired,对应<sql>上的flushCache属性,默认<select>:false;非<select>:true flushCacheIfRequired(ms); //mappedStatement.useCache == true时才使用缓存 //复习下mappedStatement.userCache,对应<sql>上的useCache属性,默认<select>:true;非<select>:false if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") //从缓存中获取cacheKey对应的value //mappedStatement.cache.get(cacheKey) List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { //缓存中没有调用BaseExecutor执行sql,这里会查询一级缓存,若不存在,查询数据库,并更新一级缓存 list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //放入二级缓存中 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
3、总结
① cacheExecutor采用策略模式,组合了一个BaseExecutor,执行的方法也是BaseExecutor的方法
② 由于CachingExecutor.createCacheKey()最终调用的是BaseExecutor.createCacheKey,所以一级缓存与二级缓存的cacheKey相同
③ 开启二级缓存后,sql执行先从查询二级缓存,后查询一级缓存,最后查询数据库。
④ 一级缓存 : BaseExecutor.localCache;二级缓存 : configuration.caches<namespace,cache>以namespace;两种cache最终数据存储在PerpetualCache.cache(Map类型)中
⑤ 虽然configuration默认的Executor是new CachingExecutor(new SimpleExecutor),但是由于在解析mapper.xml时,有<cache>或<cache-ref>标签才会创建一个cache到configuration.caches中,所以二级缓存默认是关闭的,需要到mapper.xml里面加入<cache><cache-ref>开启二级缓存
⑥ <cache>与<cache-ref>:<cache>创建一个LruCache实例,管理二级缓存的开启关闭,里面有一个PerpetualCache类型变量(装饰者模式),<cache-ref>作用主要是:一个mapper.xml可以引用另一mapper.xml的缓存达到缓存共享。
补充:LruCache与CachingExecutor的UML图类似,为什么判断一个是装饰者模式,一个是策略模式。
装饰者模式设计目的是原对象(PerpetualCache)的功能增强;策略默认设计目的原对象(Executor)抽象,可使用多个子对象(SimpleExecutor、ReuseExecutor、BatchExecutor)替换