文章目录
- 一、目标:二级缓存
- 二、设计:二级缓存
- 三、实现:二级缓存
- 3.1 工程结构
- 3.2 二级缓存类图
- 3.3 二级缓存队列
- 3.3.1 FIFI缓存策略
- 3.3.2 事务缓存
- 3.3.3 事务管理
- 3.3.4 修改一级缓存
- 3.4 缓存执行器
- 3.4.1 执行器接口
- 3.4.2 执行器抽象基类
- 3.4.3 缓存执行器
- 3.5 修改配置
- 3.5.1 映射器语句类
- 3.5.2 配置项
- 3.5.3 默认SqlSession实现类
- 3.6 映射构建器助手添加缓存
- 3.6.1 缓存构建器
- 3.6.2 映射构建器助手
- 3.7 XML配置构建器
- 3.7.1 构建器基类
- 3.7.2 注解配置构建器
- 3.7.3 XML语言构建器
- 3.7.4 XML映射构建器
- 3.7.5 XML配置构建器-全局缓存解析
- 3.7.6 XML配置构建器
- 四、测试:二级缓存
- 4.1 修改配置
- 4.1.1 修改xml配置
- 4.1.2 缓存策略配置
- 4.2 单元测试
- 五、总结:二级缓存
一、目标:二级缓存
💡 一级缓存是基于会话,那么怎么在会话结束之后依旧可以使用缓存呢?
- 关于缓存的实现,希望于当会话结束后,再发起的会话还是相同的查询操作,最好也是可以把数据从缓存中获取出来。
- 二级缓存:以一个 Mapper 为生命周期,在这个 Mapper 内的同一个操作,无论发起几次会话都可以使用缓存来处理数据。
- 之所以称为之 二级缓存,是因为它在一级缓存会话层上,添加的额外缓存操作,当会话发生
close、commit
操作时则把数据刷到二级缓存中进行保存,直至执行器发生update
操作时清空缓存。
- 之所以称为之 二级缓存,是因为它在一级缓存会话层上,添加的额外缓存操作,当会话发生
二、设计:二级缓存
💡 设计二级缓存?
- 二级缓存的重点在于无论多少个 SqlSession 会话操作同一个 SQL,不管 SqlSession 是否相同,只要 Mapper 的
namespace
相同就能共享数据。所以二级缓存也被称为namespace
级别的缓存,相当于一级缓存作用域范围更广了。 - 设计二级缓存,应该为 Mapper XML 解析后的 MappedStatement 映射器语句提供缓存服务。
- 当有会话的生命周期结束后,应该将会话的数据刷新到二级缓存中,便于后续在同
namespace
下处理相同 SQL 的操作时使用。
- 首先要在 XML 的解析中添加关于全局是否使用缓存的操作,此外因为缓存的作用域范围是在 Mapper 的
namespace
级别上,所以这里要为解析 MappedStatement 映射器语句提供缓存策略。- 注意:缓存策略一共有四种实现,包括:
LRU、FIFO、SOFT、WEAK
。
- 注意:缓存策略一共有四种实现,包括:
- 当配置了开启二级缓存服务,那么在开启会话创建执行器时,会把执行器使用缓存执行器做一层装饰器的设计使用。
- 因为需要通过这个方式将事务缓存起来,同时包装结束会话的指令
close、commit
处理一级缓存数据刷新到二级缓存中。 - 这样在下次执行相同下
namespace
以及同样的 SQL 时就可以直接从缓存中获取数据了。
- 因为需要通过这个方式将事务缓存起来,同时包装结束会话的指令
三、实现:二级缓存
3.1 工程结构
mybatis-step-18
|-src|-main| |-java| |-com.lino.mybatis| |-annotations| | |-Delete.java| | |-Insert.java| | |-Select.java| | |-Update.java| |-binding| | |-MapperMethod.java| | |-MapperProxy.java| | |-MapperProxyFactory.java| | |-MapperRegistry.java| |-builder| | |-annotations| | | |-MapperAnnotationBuilder.java| | |-xml| | | |-XMLConfigBuilder.java| | | |-XMLMapperBuilder.java| | | |-XMLStatementBuilder.java| | |-BaseBuilder.java| | |-MapperBuilderAssistant.java| | |-ParameterExpression.java| | |-ResultMapResolver.java| | |-SqlSourceBuilder.java| | |-StaticSqlSource.java| |-cache| | |-decorators| | | |-FifoCache.java| | | |-TransactionalCache.java| | |-impl| | | |-PrepetualCache.java| | |-Cache.java| | |-CacheKey.java| | |-NullCacheKey.java| | |-TransactionalCacheManager.java| |-datasource| | |-druid| | | |-DruidDataSourceFacroty.java| | |-pooled| | | |-PooledConnection.java| | | |-PooledDataSource.java| | | |-PooledDataSourceFacroty.java| | | |-PoolState.java| | |-unpooled| | | |-UnpooledDataSource.java| | | |-UnpooledDataSourceFacroty.java| | |-DataSourceFactory.java| |-executor| | |-keygen| | | |-Jdbc3KeyGenerator.java| | | |-KeyGenerator.java| | | |-NoKeyGenerator.java| | | |-SelectKeyGenerator.java| | |-parameter| | | |-ParameterHandler.java| | |-result| | | |-DefaultResultContext.java| | | |-DefaultResultHandler.java| | |-resultset| | | |-DefaultResultSetHandler.java| | | |-ResultSetHandler.java| | | |-ResultSetWrapper.java| | |-statement| | | |-BaseStatementHandler.java| | | |-PreparedStatementHandler.java| | | |-SimpleStatementHandler.java| | | |-StatementHandler.java| | |-BaseExecutor.java| | |-CachingExecutor.java| | |-ExecutionPlaceholder.java| | |-Executor.java| | |-SimpleExecutor.java| |-io| | |-Resources.java| |-mapping| | |-BoundSql.java| | |-CacheBuilder.java| | |-Environment.java| | |-MappedStatement.java| | |-ParameterMapping.java| | |-ResultFlag.java| | |-ResultMap.java| | |-ResultMapping.java| | |-SqlCommandType.java| | |-SqlSource.java| |-parsing| | |-GenericTokenParser.java| | |-TokenHandler.java| |-plugin| | |-Interceptor.java| | |-InterceptorChain.java| | |-Intercepts.java| | |-Invocation.java| | |-Plugin.java| | |-Signature.java| |-reflection| | |-factory| | | |-DefaultObjectFactory.java| | | |-ObjectFactory.java| | |-invoker| | | |-GetFieldInvoker.java| | | |-Invoker.java| | | |-MethodInvoker.java| | | |-SetFieldInvoker.java| | |-property| | | |-PropertyNamer.java| | | |-PropertyTokenizer.java| | |-wrapper| | | |-BaseWrapper.java| | | |-BeanWrapper.java| | | |-CollectionWrapper.java| | | |-DefaultObjectWrapperFactory.java| | | |-MapWrapper.java| | | |-ObjectWrapper.java| | | |-ObjectWrapperFactory.java| | |-MetaClass.java| | |-MetaObject.java| | |-Reflector.java| | |-SystemMetaObject.java| |-scripting| | |-defaults| | | |-DefaultParameterHandler.java| | | |-RawSqlSource.java| | |-xmltags| | | |-DynamicContext.java| | | |-DynamicSqlSource.java| | | |-ExpressionEvaluator.java| | | |-IfSqlNode.java| | | |-MixedSqlNode.java| | | |-OgnlCache.java| | | |-OgnlClassResolver.java| | | |-SqlNode.java| | | |-StaticTextSqlNode.java| | | |-TextSqlNode.java| | | |-TrimSqlNode.java| | | |-XMLLanguageDriver.java| | | |-XMLScriptBuilder.java| | |-LanguageDriver.java| | |-LanguageDriverRegistry.java| |-session| | |-defaults| | | |-DefaultSqlSession.java| | | |-DefaultSqlSessionFactory.java| | |-Configuration.java| | |-LocalCacheScope.java| | |-ResultContext.java| | |-ResultHandler.java| | |-RowBounds.java| | |-SqlSession.java| | |-SqlSessionFactory.java| | |-SqlSessionFactoryBuilder.java| | |-TransactionIsolationLevel.java| |-transaction| | |-jdbc| | | |-JdbcTransaction.java| | | |-JdbcTransactionFactory.java| | |-Transaction.java| | |-TransactionFactory.java| |-type| | |-BaseTypeHandler.java| | |-DateTypeHandler.java| | |-IntegerTypeHandler.java| | |-JdbcType.java| | |-LongTypeHandler.java| | |-SimpleTypeRegistry.java| | |-StringTypeHandler.java| | |-TypeAliasRegistry.java| | |-TypeHandler.java| | |-TypeHandlerRegistry.java|-test|-java| |-com.lino.mybatis.test| |-dao| | |-IActivityDao.java| |-plugin| | |-TestPlugin.java| |-po| | |-Activity.java| |-ApiTest.java|-resources|-mapper| |-Activity_Mapper.xml|-mybatis-config-datasource.xml
3.2 二级缓存类图
- 整个二级缓存的核心功能逻辑实现,主要以体现在实现 Cache 缓存接口,提供 Mapper XML 解析作用域
namespace
范围的缓存队列的使用上。 - 通过提供装饰执行器实现模式的 CacheingExecutor 二级缓存执行器,包括会话事务缓存操作,在当会话以
close、commit
方式结束时,将缓存刷新到二级缓存队列中,便于下次相同作用域范围下的同一个查询,可以直接从二级缓存中获取数据。
3.3 二级缓存队列
- Mybatis 对二级缓存的设计非常灵活,你可以通过配置对缓存的策略做一系列的调整,包括缓存策略:
LRU、FIFO、SOFT、WEAK
。LRU
:最近很少使用,主动移除最长时间不被使用的缓存对象。LRU
也是默认的缓存策略。FIFO
:先进先出,按对象进入缓存的顺序移除过期对象。SOFT
:软引用,基于垃圾回收器状态和软引用规则移除对象。WEAK
:弱引用,更主动的基于垃圾回收器状态和弱引用规则移除对象。
- 同时也提供了相应的数据刷新策略、对象存储限制等。
- 除此之外,Mybatis 也支持用户自己实现以及跟第三方内存缓存库做集成使用。
3.3.1 FIFI缓存策略
FifoCache.java
package com.lino.mybatis.cache.decorators;import com.lino.mybatis.cache.Cache;
import java.util.Deque;
import java.util.LinkedList;/*** @description: FIFI(first in, first out) cache decorator*/
public class FifoCache implements Cache {private final Cache delegate;private Deque<Object> keyList;private int size;public FifoCache(Cache delegate) {this.delegate = delegate;this.keyList = new LinkedList<>();this.size = 1024;}@Overridepublic String getId() {return delegate.getId();}@Overridepublic void putObject(Object key, Object value) {cycleKeyList(key);delegate.putObject(key, value);}@Overridepublic Object getObject(Object key) {return delegate.getObject(key);}@Overridepublic Object removeObject(Object key) {return delegate.removeObject(key);}@Overridepublic void clear() {delegate.clear();keyList.clear();}@Overridepublic int getSize() {return delegate.getSize();}public void setSize(int size) {this.size = size;}private void cycleKeyList(Object key) {keyList.addLast(key);if (keyList.size() > size) {Object oldestKey = keyList.removeFirst();delegate.removeObject(oldestKey);}}
}
FIFO
先进先出队列,基于Deque
维护了一个链表,其他的操作都包装给 Cache 去完成,属于典型的装饰器模式。- 在 FifoCache 所提供的方法实现比较简单,主要包括:存放、获取、移除、清空队列。
- 另外
cycleKeyList
方法的作用是在增加记录时判断记录是否超过size
值,以此移除链表的第一个元素,从而达到FIFO
缓存效果。
3.3.2 事务缓存
- TransactionalCache 所保存的是会话期间内的缓存数据,当会话结束后则把缓存刷新到二级缓存中。如果是回滚操作则清空缓存。
TransactionalCache.java
package com.lino.mybatis.cache.decorators;import com.lino.mybatis.cache.Cache;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** @description: 事务缓存*/
public class TransactionalCache implements Cache {private Cache delegate;/*** commit时要不要清缓存*/private boolean clearOnCommit;/*** commit 时要添加的元素*/private Map<Object, Object> entriesToAddOnCommit;private Set<Object> entriesMissedInCache;public TransactionalCache(Cache delegate) {// delegate = FifoCachethis.delegate = delegate;// 默认 commit 时不清缓存this.clearOnCommit = false;this.entriesToAddOnCommit = new HashMap<>();this.entriesMissedInCache = new HashSet<>();}@Overridepublic String getId() {return delegate.getId();}@Overridepublic void putObject(Object key, Object value) {entriesToAddOnCommit.put(key, value);}@Overridepublic Object getObject(Object key) {// key: CacheKey 拼装后的哈希码Object object = delegate.getObject(key);if (object == null) {entriesMissedInCache.add(key);}return clearOnCommit ? null : object;}@Overridepublic Object removeObject(Object key) {return null;}@Overridepublic void clear() {clearOnCommit = true;entriesToAddOnCommit.clear();}@Overridepublic int getSize() {return delegate.getSize();}public void commit() {if (clearOnCommit) {delegate.clear();}flushPendingEntries();reset();}public void rollback() {unlockMissedEntries();reset();}public void reset() {clearOnCommit = false;entriesToAddOnCommit.clear();entriesMissedInCache.clear();}/*** 刷新数据到 MappedStatement#Cache 中,也就是把数据填充到 Mapper XML 级别下。* flushPendingEntries 方法把事务缓存下的数据,填充到 FifoCache 中。*/private void flushPendingEntries() {for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {delegate.putObject(entry.getKey(), entry.getValue());}for (Object entry : entriesMissedInCache) {if (!entriesToAddOnCommit.containsKey(entry)) {delegate.putObject(entry, null);}}}private void unlockMissedEntries() {for (Object entry : entriesMissedInCache) {delegate.putObject(entry, null);}}
}
- TransactionalCache 事务缓存提供了对一级缓存的数据存放和使用的操作。
- 当一级缓存作用域范围的会话因为
commit、close
结束,则会调用到flushPengdingEntries
方法。 - 通过循环处理调用
delegate.putObject(entry.getKey(), entry.getValue())
,把数据刷新到二级缓存队列中。 - 另外
rollback
回滚方法则是一种清空缓存操作。
- 当一级缓存作用域范围的会话因为
3.3.3 事务管理
TransactionalCacheManager
package com.lino.mybatis.cache;import com.lino.mybatis.cache.decorators.TransactionalCache;
import java.util.HashMap;
import java.util.Map;/*** @description: 事务缓存管理器*/
public class TransactionalCacheManager {private Map<Cache, TransactionalCache> transactionCaches = new HashMap<>(16);public void clear(Cache cache) {getTransactionalCache(cache).clear();}/*** 得到某个TransactionalCache的值*/public Object getObject(Cache cache, CacheKey key) {return getTransactionalCache(cache).getObject(key);}public void putObject(Cache cache, CacheKey key, Object value) {getTransactionalCache(cache).putObject(key, value);}/*** 提交时全部提交*/public void commit() {for (TransactionalCache txCache : transactionCaches.values()) {txCache.commit();}}/*** 回滚时全部回滚*/public void rollback() {for (TransactionalCache txCache : transactionCaches.values()) {txCache.rollback();}}private TransactionalCache getTransactionalCache(Cache cache) {TransactionalCache txCache = transactionCaches.get(cache);if (txCache == null) {txCache = new TransactionalCache(cache);transactionCaches.put(cache, txCache);}return txCache;}
}
- 事务缓存管理器是对事务缓存的包装操作。
- 用于在缓存执行器创建期间实例化,包装执行期内的所有事务缓存操作,做批量的提交和回滚时缓存数据刷新的处理。
3.3.4 修改一级缓存
PerpetualCache.java
package com.lino.mybatis.cache.impl;import com.lino.mybatis.cache.Cache;import java.util.HashMap;
import java.util.Map;/*** @description: 一级缓存,在 Session 生命周期内一直保持,每创建新的 OpenSession 都会创建一个缓存器 PerpetualCache*/
public class PerpetualCache implements Cache {private String id;/*** 使用HashMap存放一级缓存数据,session 生命周期较短,正常情况下数据不会一直在缓存存放*/private Map<Object, Object> cache = new HashMap<>(16);public PerpetualCache(String id) {this.id = id;}@Overridepublic String getId() {return id;}@Overridepublic void putObject(Object key, Object value) {cache.put(key, value);}@Overridepublic Object getObject(Object key) {return cache.get(key);}@Overridepublic Object removeObject(Object key) {return cache.remove(key);}@Overridepublic void clear() {cache.clear();}@Overridepublic int getSize() {return cache.size();}
}
- 移除一级缓存日志打印
3.4 缓存执行器
3.4.1 执行器接口
Executor.java
package com.lino.mybatis.executor;import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import java.sql.SQLException;
import java.util.List;/*** @description: 执行器*/
public interface Executor {/*** 结果处理器*/ResultHandler NO_RESULT_HANDLER = null;/*** 更新** @param ms 映射器语句* @param parameter 参数* @return 返回的是受影响的行数* @throws SQLException SQL异常*/int update(MappedStatement ms, Object parameter) throws SQLException;/*** 查询,含缓存** @param ms 映射器语句* @param parameter 参数* @param rowBounds 分页记录限制* @param resultHandler 结果处理器* @param key 缓存key* @param boundSql SQL对象* @param <E> 返回的类型* @return List<E>* @throws SQLException SQL异常*/<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException;/*** 查询** @param ms 映射器语句* @param parameter 参数* @param rowBounds 分页记录限制* @param resultHandler 结果处理器* @param <E> 返回的类型* @return List<E>* @throws SQLException SQL异常*/<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;/*** 获取事务** @return 事务对象*/Transaction getTransaction();/*** 提交** @param required 是否请求执行* @throws SQLException SQL异常*/void commit(boolean required) throws SQLException;/*** 回滚** @param required 是否请求执行* @throws SQLException SQL异常*/void rollback(boolean required) throws SQLException;/*** 关闭** @param forceRollback 是否强制回滚*/void close(boolean forceRollback);/*** 清理session缓存*/void clearLocalCache();/*** 创建缓存key** @param ms 映射器语句* @param parameterObject 参数对象* @param rowBounds 分页记录限制* @param boundSql SQL对象* @return 缓存key*/CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);/*** 设置执行器** @param executor 执行器*/void setExecutorWrapper(Executor executor);
}
- 添加
setExecutorWrapper
设置执行器方法。
3.4.2 执行器抽象基类
BaseExecutor.java
package com.lino.mybatis.executor;import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ParameterMapping;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.type.TypeHandlerRegistry;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;/*** @description: 执行器抽象基类*/
public abstract class BaseExecutor implements Executor {...@Overridepublic void setExecutorWrapper(Executor executor) {this.wrapper = wrapper;}@Overridepublic void close(boolean forceRollback) {try {try {rollback(forceRollback);} finally {transaction.close();}} catch (SQLException e) {logger.warn("Unexpected exception on closing transaction. Cause: " + e);} finally {transaction = null;localCache = null;closed = true;}}...
}
3.4.3 缓存执行器
- 缓存执行器是一个装饰器模式,将 SimpleExecutor 做一层包装,提供缓存的能力。
- 因为这样的包装后就可以将 SimpleExecutor 中的一级缓存以及相应的能力进行使用,在二级缓存
CachingExecutor
执行器中完成缓存在会话周期内的流转操作。
CachingExecutor.java
package com.lino.mybatis.executor;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.CacheKey;
import com.lino.mybatis.cache.TransactionalCacheManager;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.transaction.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;/*** @description: 二级缓存执行器*/
public class CachingExecutor implements Executor {private Logger logger = LoggerFactory.getLogger(BaseExecutor.class);private Executor delegate;private TransactionalCacheManager tcm = new TransactionalCacheManager();public CachingExecutor(Executor delegate) {this.delegate = delegate;delegate.setExecutorWrapper(this);}@Overridepublic int update(MappedStatement ms, Object parameter) throws SQLException {return delegate.update(ms, parameter);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {Cache cache = ms.getCache();if (cache != null) {flushCacheIfRequired(ms);if (ms.isUseCache() && resultHandler == null) {@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {list = delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);// cache:缓存队列实现类,FIFO// key:哈希值 [mappedStatementId + offset + limit + SQL + queryParams + environment]// list:查询的数据tcm.putObject(cache, key, list);}// 打印调试日志,记录二级缓存获取数据if (logger.isDebugEnabled() && cache.getSize() > 0) {logger.debug("二级缓存: {}", JSON.toJSONString(list));}return list;}}return delegate.query(ms, parameter, rowBounds, resultHandler, key, boundSql);}@Overridepublic <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 1.获取绑定SQLBoundSql boundSql = ms.getBoundSql(parameter);// 2.创建缓存keyCacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);return query(ms, parameter, rowBounds, resultHandler, key, boundSql);}@Overridepublic Transaction getTransaction() {return delegate.getTransaction();}@Overridepublic void commit(boolean required) throws SQLException {delegate.commit(required);tcm.commit();}@Overridepublic void rollback(boolean required) throws SQLException {try {delegate.rollback(required);} finally {if (required) {tcm.rollback();}}}@Overridepublic void close(boolean forceRollback) {try {if (forceRollback) {tcm.rollback();} else {tcm.commit();}} finally {delegate.close(forceRollback);}}@Overridepublic void clearLocalCache() {delegate.clearLocalCache();}@Overridepublic CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);}@Overridepublic void setExecutorWrapper(Executor executor) {throw new UnsupportedOperationException("This method should not be called");}private void flushCacheIfRequired(MappedStatement ms) {Cache cache = ms.getCache();if (cache != null && ms.isFlushCacheRequired()) {tcm.clear(cache);}}
}
- CachingExecutor 实现类中主要注意的点是会话中数据查询时的缓存使用,在
query
方法中执行的delegate.<E>query
操作。 - 其实这个
delegate
就是 SimpleExecutor 实例化的对象,当缓存数据随着会话周期处理完后,则存放到 MappedStatement 所提供的 Cache 缓存队列中,也就是 FifoCache 先进先出缓存实现类。 - 另外关于缓存的流转会调用 TransactionalCacheManager 事务缓存管理器进行操作,从会话作用域范围,通过会话的结束,刷新提交到二级缓存或者清空处理。
3.5 修改配置
3.5.1 映射器语句类
MappedStatement
package com.lino.mybatis.mapping;import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import java.util.Collections;
import java.util.List;/*** @description: 映射器语句类*/
public class MappedStatement {private String resource;private Configuration configuration;private String id;private SqlCommandType sqlCommandType;private SqlSource sqlSource;Class<?> resultType;private LanguageDriver lang;private List<ResultMap> resultMaps;private boolean flushCacheRequired;private KeyGenerator keyGenerator;private String[] keyProperties;private String[] keyColumns;private Cache cache;private boolean useCache;public MappedStatement() {}/*** 获取SQL对象** @param parameterObject 参数* @return SQL对象*/public BoundSql getBoundSql(Object parameterObject) {// 调用 SqlSource#getBoundSqlreturn sqlSource.getBoundSql(parameterObject);}public static class Builder {private MappedStatement mappedStatement = new MappedStatement();public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType, SqlSource sqlSource, Class<?> resultType) {mappedStatement.configuration = configuration;mappedStatement.id = id;mappedStatement.sqlCommandType = sqlCommandType;mappedStatement.sqlSource = sqlSource;mappedStatement.resultType = resultType;mappedStatement.keyGenerator = configuration.isUseGeneratedKeys()&& SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();}public MappedStatement build() {assert mappedStatement.configuration != null;assert mappedStatement.id != null;mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);return mappedStatement;}public Builder resource(String resource) {mappedStatement.resource = resource;return this;}public String id() {return mappedStatement.id;}public Builder resultMaps(List<ResultMap> resultMaps) {mappedStatement.resultMaps = resultMaps;return this;}public Builder keyGenerator(KeyGenerator keyGenerator) {mappedStatement.keyGenerator = keyGenerator;return this;}public Builder keyProperty(String keyProperty) {mappedStatement.keyProperties = delimitedStringToArray(keyProperty);return this;}public Builder cache(Cache cache) {mappedStatement.cache = cache;return this;}public Builder flushCacheRequired(boolean flushCacheRequired) {mappedStatement.flushCacheRequired = flushCacheRequired;return this;}public Builder useCache(boolean useCache) {mappedStatement.useCache = useCache;return this;}}private static String[] delimitedStringToArray(String in) {if (in == null || in.trim().length() == 0) {return null;} else {return in.split(",");}}public Configuration getConfiguration() {return configuration;}public String getId() {return id;}public SqlCommandType getSqlCommandType() {return sqlCommandType;}public SqlSource getSqlSource() {return sqlSource;}public Class<?> getResultType() {return resultType;}public LanguageDriver getLang() {return lang;}public List<ResultMap> getResultMaps() {return resultMaps;}public String[] getKeyProperties() {return keyProperties;}public KeyGenerator getKeyGenerator() {return keyGenerator;}public String getResource() {return resource;}public String[] getKeyColumns() {return keyColumns;}public boolean isFlushCacheRequired() {return flushCacheRequired;}public Cache getCache() {return cache;}public boolean isUseCache() {return useCache;}
}
- 添加 Cache 缓存对象
3.5.2 配置项
Configuration.java
package com.lino.mybatis.session;import com.lino.mybatis.binding.MapperRegistry;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.datasource.druid.DruidDataSourceFactory;
import com.lino.mybatis.datasource.pooled.PooledDataSourceFactory;
import com.lino.mybatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.lino.mybatis.executor.CachingExecutor;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.executor.SimpleExecutor;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.parameter.ParameterHandler;
import com.lino.mybatis.executor.resultset.DefaultResultSetHandler;
import com.lino.mybatis.executor.resultset.ResultSetHandler;
import com.lino.mybatis.executor.statement.PreparedStatementHandler;
import com.lino.mybatis.executor.statement.StatementHandler;
import com.lino.mybatis.mapping.BoundSql;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.plugin.InterceptorChain;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.factory.DefaultObjectFactory;
import com.lino.mybatis.reflection.factory.ObjectFactory;
import com.lino.mybatis.reflection.wrapper.DefaultObjectWrapperFactory;
import com.lino.mybatis.reflection.wrapper.ObjectWrapperFactory;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.scripting.LanguageDriverRegistry;
import com.lino.mybatis.scripting.xmltags.XMLLanguageDriver;
import com.lino.mybatis.transaction.Transaction;
import com.lino.mybatis.transaction.jdbc.JdbcTransactionFactory;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandlerRegistry;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;/*** @description: 配置项* @author: lingjian* @createDate: 2022/11/7 21:32*/
public class Configuration {/*** 环境*/protected Environment environment;/*** 是否使用自动生成键值对*/protected boolean useGeneratedKeys = false;/*** 默认启用缓存,cacheEnabled = true/false*/protected boolean cacheEnabled = true;/*** 缓存机制,默认不配置的情况是 SESSION*/protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;/*** 映射注册机*/protected MapperRegistry mapperRegistry = new MapperRegistry(this);/*** 映射的语句,存在Map里*/protected final Map<String, MappedStatement> mappedStatements = new HashMap<>(16);/*** 缓存,存在Map里*/protected final Map<String, Cache> caches = new HashMap<>(16);/*** 结果映射,存在Map里*/protected final Map<String, ResultMap> resultMaps = new HashMap<>(16);/*** 键值生成器,存在Map里*/protected final Map<String, KeyGenerator> keyGenerators = new HashMap<>(16);/*** 插件拦截器链*/protected final InterceptorChain interceptorChain = new InterceptorChain();/*** 类型别名注册机*/protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();/*** 脚本语言注册器*/protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();/*** 类型处理器注册机*/protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();/*** 对象工厂*/protected ObjectFactory objectFactory = new DefaultObjectFactory();/*** 对象包装工厂*/protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();/*** 准备资源列表*/protected final Set<String> loadedResources = new HashSet<>();/*** 数据库ID*/protected String databaseId;public Configuration() {typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);typeAliasRegistry.registerAlias("FIFO", FifoCache.class);languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);}public void addMappers(String packageName) {mapperRegistry.addMappers(packageName);}public <T> void addMapper(Class<T> type) {mapperRegistry.addMapper(type);}public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}public boolean hasMapper(Class<?> type) {return mapperRegistry.hasMapper(type);}public void addMappedStatement(MappedStatement ms) {mappedStatements.put(ms.getId(), ms);}public MappedStatement getMappedStatement(String id) {return mappedStatements.get(id);}public TypeAliasRegistry getTypeAliasRegistry() {return typeAliasRegistry;}public Environment getEnvironment() {return environment;}public void setEnvironment(Environment environment) {this.environment = environment;}public String getDatabaseId() {return databaseId;}public ObjectFactory getObjectFactory() {return objectFactory;}public boolean isUseGeneratedKeys() {return useGeneratedKeys;}public void setUseGeneratedKeys(boolean useGeneratedKeys) {this.useGeneratedKeys = useGeneratedKeys;}public LocalCacheScope getLocalCacheScope() {return localCacheScope;}public void setLocalCacheScope(LocalCacheScope localCacheScope) {this.localCacheScope = localCacheScope;}/*** 生产执行器** @param transaction 事务* @return 执行器*/public Executor newExecutor(Transaction transaction) {Executor executor = new SimpleExecutor(this, transaction);// 配置开启缓存,创建 CachingExecutor(默认就是有缓存)装饰着模式if (cacheEnabled) {executor = new CachingExecutor(executor);}return executor;}/*** 创建语句处理器** @param executor 执行器* @param mappedStatement 映射器语句类* @param parameter 参数* @param rowBounds 分页记录限制* @param resultHandler 结果处理器* @param boundSql SQL语句* @return StatementHandler 语句处理器*/public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new PreparedStatementHandler(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);// 嵌入插件,代理对象statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}/*** 创建结果集处理器** @param executor 执行器* @param mappedStatement 映射器语句类* @param boundSql SQL语句* @return ResultSetHandler 结果集处理器*/public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {return new DefaultResultSetHandler(executor, mappedStatement, resultHandler, rowBounds, boundSql);}/*** 创建元对象** @param object 原对象* @return 元对象*/public MetaObject newMetaObject(Object object) {return MetaObject.forObject(object, objectFactory, objectWrapperFactory);}/*** 创建类型处理器注册机** @return TypeHandlerRegistry 类型处理器注册机*/public TypeHandlerRegistry getTypeHandlerRegistry() {return typeHandlerRegistry;}/*** 是否包含资源** @param resource 资源* @return 是否*/public boolean isResourceLoaded(String resource) {return loadedResources.contains(resource);}/*** 添加资源** @param resource 资源*/public void addLoadedResource(String resource) {loadedResources.add(resource);}/*** 获取脚本语言注册机** @return languageRegistry 脚本语言注册机*/public LanguageDriverRegistry getLanguageRegistry() {return languageRegistry;}/*** 获取参数处理器** @param mappedStatement 映射器语言类型* @param parameterObject 参数对象* @param boundSql SQL语句* @return ParameterHandler参数处理器*/public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {// 创建参数处理器ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);return parameterHandler;}/*** 获取默认脚本语言驱动** @return 脚本语言驱动*/public LanguageDriver getDefaultScriptingLanguageInstance() {return languageRegistry.getDefaultDriver();}public ResultMap getResultMap(String id) {return resultMaps.get(id);}public void addResultMap(ResultMap resultMap) {resultMaps.put(resultMap.getId(), resultMap);}public void addKeyGenerator(String id, KeyGenerator keyGenerator) {keyGenerators.put(id, keyGenerator);}public KeyGenerator getKeyGenerator(String id) {return keyGenerators.get(id);}public boolean hasKeyGenerators(String id) {return keyGenerators.containsKey(id);}public void addInterceptor(Interceptor interceptorInstance) {interceptorChain.addInterceptor(interceptorInstance);}public boolean isCacheEnabled() {return cacheEnabled;}public void setCacheEnabled(boolean cacheEnabled) {this.cacheEnabled = cacheEnabled;}public void addCache(Cache cache) {caches.put(cache.getId(), cache);}public Cache getCache(String id) {return caches.get(id);}}
newExecutor
装饰缓存执行。
3.5.3 默认SqlSession实现类
DefaultSqlSession.java
package com.lino.mybatis.session.defaults;import com.alibaba.fastjson.JSON;
import com.lino.mybatis.executor.Executor;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.RowBounds;
import com.lino.mybatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.List;/*** @description: 默认sqlSession实现类*/
public class DefaultSqlSession implements SqlSession {...@Overridepublic void close() {executor.close(false);}...
}
3.6 映射构建器助手添加缓存
3.6.1 缓存构建器
CacheBuilder.java
package com.lino.mybatis.mapping;import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.reflection.MetaObject;
import com.lino.mybatis.reflection.SystemMetaObject;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;/*** @description: 缓存构建器, 建造者模式*/
public class CacheBuilder {private String id;private Class<? extends Cache> implementation;private List<Class<? extends Cache>> decorators;private Integer size;private Long clearInterval;private boolean readWrite;private Properties properties;private boolean blocking;public CacheBuilder(String id) {this.id = id;this.decorators = new ArrayList<>();}public CacheBuilder implementation(Class<? extends Cache> implementation) {this.implementation = implementation;return this;}public CacheBuilder addDecorator(Class<? extends Cache> decorator) {if (decorator != null) {this.decorators.add(decorator);}return this;}public CacheBuilder size(Integer size) {this.size = size;return this;}public CacheBuilder clearInterval(Long clearInterval) {this.clearInterval = clearInterval;return this;}public CacheBuilder readWrite(boolean readWrite) {this.readWrite = readWrite;return this;}public CacheBuilder blocking(boolean blocking) {this.blocking = blocking;return this;}public CacheBuilder properties(Properties properties) {this.properties = properties;return this;}public Cache build() {setDefaultImplementations();Cache cache = newBaseCacheInstance(implementation, id);setCacheProperties(cache);if (PerpetualCache.class.equals(cache.getClass())) {for (Class<? extends Cache> decorator : decorators) {// 使用装饰者模式包装cache = newCacheDecoratorInstance(decorator, cache);// 额外属性设置setCacheProperties(cache);}}return cache;}private void setDefaultImplementations() {if (implementation == null) {implementation = PerpetualCache.class;if (decorators.isEmpty()) {decorators.add(FifoCache.class);}}}private void setCacheProperties(Cache cache) {if (properties != null) {MetaObject metaCache = SystemMetaObject.forObject(cache);for (Map.Entry<Object, Object> entry : properties.entrySet()) {String name = (String) entry.getKey();String value = (String) entry.getValue();if (metaCache.hasSetter(name)) {Class<?> type = metaCache.getSetterType(name);if (String.class == type) {metaCache.setValue(name, value);} else if (int.class == type || Integer.class == type) {metaCache.setValue(name, Integer.valueOf(value));} else if (long.class == type || Long.class == type) {metaCache.setValue(name, Long.valueOf(value));} else if (short.class == type || Short.class == type) {metaCache.setValue(name, Short.valueOf(value));} else if (byte.class == type || Byte.class == type) {metaCache.setValue(name, Byte.valueOf(value));} else if (float.class == type || Float.class == type) {metaCache.setValue(name, Float.valueOf(value));} else if (boolean.class == type || Boolean.class == type) {metaCache.setValue(name, Boolean.valueOf(value));} else if (double.class == type || Double.class == type) {metaCache.setValue(name, Double.valueOf(value));} else {throw new RuntimeException("Unsupported property type for cache: '" + name + "' of type " + type);}}}}}private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);try {return cacheConstructor.newInstance(id);} catch (Exception e) {throw new RuntimeException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);}}private Constructor<? extends Cache> getBaseCacheConstructor(Class<? extends Cache> cacheClass) {try {return cacheClass.getConstructor(String.class);} catch (Exception e) {throw new RuntimeException("Invalid base cache implementation (" + cacheClass + "). " +"Base cache implementations must have a constructor that takes a String id as a parameter. Cause: " + e, e);}}private Cache newCacheDecoratorInstance(Class<? extends Cache> cacheClass, Cache base) {Constructor<? extends Cache> cacheConstructor = getCacheDecoratorConstructor(cacheClass);try {return cacheConstructor.newInstance(base);} catch (Exception e) {throw new RuntimeException("Could not instantiate cache decorator (" + cacheClass + "). Cause: " + e, e);}}private Constructor<? extends Cache> getCacheDecoratorConstructor(Class<? extends Cache> cacheClass) {try {return cacheClass.getConstructor(Cache.class);} catch (Exception e) {throw new RuntimeException("Invalid cache decorator (" + cacheClass + "). " +"Cache decorators must have a constructor that takes a Cache instance as a parameter. Cause: " + e, e);}}
}
3.6.2 映射构建器助手
MapperBuilderAssistant.java
package com.lino.mybatis.builder;import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.cache.decorators.FifoCache;
import com.lino.mybatis.cache.impl.PerpetualCache;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.mapping.*;
import com.lino.mybatis.reflection.MetaClass;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;/*** @description: 映射构建器助手,建造者*/
public class MapperBuilderAssistant extends BaseBuilder {private String currentNamespace;private String resource;private Cache currentCache;public MapperBuilderAssistant(Configuration configuration, String resource) {super(configuration);this.resource = resource;}public ResultMapping buildResultMapping(Class<?> resultType, String property, String column, List<ResultFlag> flags) {Class<?> javaTypeClass = resolveResultJavaType(resultType, property, null);TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, null);ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);builder.typeHandler(typeHandlerInstance);builder.flags(flags);return builder.build();}private Class<?> resolveResultJavaType(Class<?> resultType, String property, Class<?> javaType) {if (javaType == null && property != null) {try {MetaClass metaResultType = MetaClass.forClass(resultType);javaType = metaResultType.getSetterType(property);} catch (Exception ignore) {}}if (javaType == null) {javaType = Object.class;}return javaType;}public String getCurrentNamespace() {return currentNamespace;}public void setCurrentNamespace(String currentNamespace) {this.currentNamespace = currentNamespace;}public String applyCurrentNamespace(String base, boolean isReference) {if (base == null) {return null;}if (isReference) {if (base.contains(".")) {return base;}} else {if (base.startsWith(currentNamespace + ".")) {return base;}if (base.contains(".")) {throw new RuntimeException("Dots are not allowed in element names, please remove it from " + base);}}return currentNamespace + "." + base;}/*** 添加映射器语句*/public MappedStatement addMappedStatement(String id, SqlSource sqlSource, SqlCommandType sqlCommandType,Class<?> parameterType, String resultMap, Class<?> resultType,boolean flushCache, boolean useCache,KeyGenerator keyGenerator, String keyProperty, LanguageDriver lang) {// 给id加上namespace前缀:com.lino.mybatis.test.dao.IUserDao.queryUserInfoByIdid = applyCurrentNamespace(id, false);// 是否时select语句boolean isSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlCommandType, sqlSource, resultType);statementBuilder.resource(resource);statementBuilder.keyGenerator(keyGenerator);statementBuilder.keyProperty(keyProperty);// 结果映射, 给 MappedStatement#resultMapssetStatementResultMap(resultMap, resultType, statementBuilder);setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);MappedStatement statement = statementBuilder.build();// 映射语句信息,建造完存放到配置项中configuration.addMappedStatement(statement);return statement;}private void setStatementCache(boolean isSelect, boolean flushCache, boolean useCache, Cache cache, MappedStatement.Builder statementBuilder) {flushCache = valueOrDefault(flushCache, !isSelect);useCache = valueOrDefault(useCache, !isSelect);statementBuilder.flushCacheRequired(flushCache);statementBuilder.useCache(useCache);statementBuilder.cache(cache);}private void setStatementResultMap(String resultMap, Class<?> resultType, MappedStatement.Builder statementBuilder) {// 因为暂时还没有在 Mapper XML 中配置 Map 返回结果,所以这里返回的是 nullresultMap = applyCurrentNamespace(resultMap, true);List<ResultMap> resultMaps = new ArrayList<>();if (resultMap != null) {String[] resultMapNames = resultMap.split(",");for (String resultMapName : resultMapNames) {resultMaps.add(configuration.getResultMap(resultMapName.trim()));}}/** 通常使用 resultType 即可满足大部分场景* <select id="queryUserInfoById" resultType="com.lino.mybatis.test.po.User">* 使用 resultType 的情况下,Mybatis 会自动创建一个 ResultMap,基于属性名称映射列到 JavaBean 的属性上。*/else if (resultType != null) {ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, statementBuilder.id() + "-Inline", resultType, new ArrayList<>());resultMaps.add(inlineResultMapBuilder.build());}statementBuilder.resultMaps(resultMaps);}public ResultMap addResultMap(String id, Class<?> type, List<ResultMapping> resultMappings) {// 补全ID全路径,如:com.lino.mybatis.test.dao.IActivityDao + activityMapid = applyCurrentNamespace(id, false);ResultMap.Builder inlineResultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings);ResultMap resultMap = inlineResultMapBuilder.build();configuration.addResultMap(resultMap);return resultMap;}public Cache useNewCache(Class<? extends Cache> typeClass,Class<? extends Cache> evictionClass,Long flushInterval,Integer size,boolean readWrite,boolean blocking,Properties props) {// 判断为null,则用默认值typeClass = valueOrDefault(typeClass, PerpetualCache.class);evictionClass = valueOrDefault(evictionClass, FifoCache.class);// 建造者模式构建 cache [currentNamespace=com.lino.mybatis.test.dao.IActivityDao]Cache cache = new CacheBuilder(currentNamespace).implementation(typeClass).addDecorator(evictionClass).clearInterval(flushInterval).size(size).readWrite(readWrite).blocking(blocking).properties(props).build();// 添加缓存configuration.addCache(cache);currentCache = cache;return cache;}private <T> T valueOrDefault(T value, T defaultValue) {return value == null ? defaultValue : value;}
}
3.7 XML配置构建器
3.7.1 构建器基类
BaseBuilder.java
package com.lino.mybatis.builder;import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.type.TypeAliasRegistry;
import com.lino.mybatis.type.TypeHandler;
import com.lino.mybatis.type.TypeHandlerRegistry;/*** @description: 构建器的基类,建造者模式*/
public class BaseBuilder {protected final Configuration configuration;protected final TypeAliasRegistry typeAliasRegistry;protected final TypeHandlerRegistry typeHandlerRegistry;...protected Boolean booleanValueOf(String value, Boolean defaultValue) {return value == null ? defaultValue : Boolean.valueOf(value);}
}
3.7.2 注解配置构建器
MapperAnnotationBuilder.java
package com.lino.mybatis.builder.annotation;import com.lino.mybatis.annotations.Delete;
import com.lino.mybatis.annotations.Insert;
import com.lino.mybatis.annotations.Select;
import com.lino.mybatis.annotations.Update;
import com.lino.mybatis.binding.MapperMethod;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.ResultHandler;
import com.lino.mybatis.session.RowBounds;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;/*** @description: 注解配置构建器 Mapper*/
public class MapperAnnotationBuilder {.../*** 解析语句** @param method 方法*/private void parseStatement(Method method) {Class<?> parameterTypeClass = getParameterType(method);LanguageDriver languageDriver = getLanguageDriver(method);SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);if (sqlSource != null) {final String mappedStatementId = type.getName() + "." + method.getName();SqlCommandType sqlCommandType = getSqlCommandType(method);KeyGenerator keyGenerator;String keyProperty = "id";if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();} else {keyGenerator = new NoKeyGenerator();}boolean isSelect = sqlCommandType == SqlCommandType.SELECT;String resultMapId = null;if (isSelect) {resultMapId = parseResultMap(method);}// 调用助手类assistant.addMappedStatement(mappedStatementId,sqlSource,sqlCommandType,parameterTypeClass,resultMapId,getReturnType(method),false,false,keyGenerator,keyProperty,languageDriver);}}...}
3.7.3 XML语言构建器
XMLStatementBuilder.java
package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.executor.keygen.Jdbc3KeyGenerator;
import com.lino.mybatis.executor.keygen.KeyGenerator;
import com.lino.mybatis.executor.keygen.NoKeyGenerator;
import com.lino.mybatis.executor.keygen.SelectKeyGenerator;
import com.lino.mybatis.mapping.MappedStatement;
import com.lino.mybatis.mapping.SqlCommandType;
import com.lino.mybatis.mapping.SqlSource;
import com.lino.mybatis.scripting.LanguageDriver;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Element;
import java.util.List;
import java.util.Locale;/*** @description: XML语言构建器*/
public class XMLStatementBuilder extends BaseBuilder {.../*** 解析语句(select|insert|update|delete)* <select* id="selectPerson"* parameterType="int"* parameterMap="deprecated"* resultType="hashmap"* resultMap="personResultMap"* flushCache="false"* useCache="true"* timeout="10000"* fetchSize="256"* statementType="PREPARED"* resultSetType="FORWARD_ONLY">* SELECT * FROM PERSON WHERE ID = #{id}* </select>*/public void parseStatementNode() {String id = element.attributeValue("id");// 参数类型String parameterType = element.attributeValue("parameterType");Class<?> parameterTypeClass = resolveAlias(parameterType);// 外部应用 resultMapString resultMap = element.attributeValue("resultMap");// 结果类型String resultType = element.attributeValue("resultType");Class<?> resultTypeClass = resolveAlias(resultType);// 获取命令类型(select|insert|update|delete)String nodeName = element.getName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = Boolean.parseBoolean(element.attributeValue("flushCache", String.valueOf(!isSelect)));boolean useCache = Boolean.parseBoolean(element.attributeValue("useCache", String.valueOf(isSelect)));// 获取默认语言驱动器Class<?> langClass = configuration.getLanguageRegistry().getDefaultDriverClass();LanguageDriver langDriver = configuration.getLanguageRegistry().getDriver(langClass);// 解析<selectKey>processSelectKeyNodes(id, parameterTypeClass, langDriver);// 解析成SqlSource,DynamicSqlSource/RawSqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, element, parameterTypeClass);// 属性标记【仅对insert有用】,MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值String keyProperty = element.attributeValue("keyProperty");KeyGenerator keyGenerator = null;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerators(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? new Jdbc3KeyGenerator() :new NoKeyGenerator();}// 调用助手类builderAssistant.addMappedStatement(id,sqlSource,sqlCommandType,parameterTypeClass,resultMap,resultTypeClass,flushCache,useCache,keyGenerator,keyProperty,langDriver);}.../*** <selectKey keyProperty="id" order="AFTER" resultType="long">* SELECT LAST_INSERT_ID()* </selectKey>*/private void parseSelectKeyNode(String id, Element nodeToHandle, Class<?> parameterTypeClass, LanguageDriver langDriver) {String resultType = nodeToHandle.attributeValue("resultType");Class<?> resultTypeClass = resolveClass(resultType);boolean executeBefore = "BEFORE".equals(nodeToHandle.attributeValue("order", "AFTER"));String keyProperty = nodeToHandle.attributeValue("keyProperty");// 默认String resultMap = null;boolean flushCache = false;boolean useCache = false;KeyGenerator keyGenerator = new NoKeyGenerator();// 解析成SqlSource,DynamicSqlSource/RawSqlSourceSqlSource sqlSource = langDriver.createSqlSource(configuration, nodeToHandle, parameterTypeClass);SqlCommandType sqlCommandType = SqlCommandType.SELECT;// 调用助手类builderAssistant.addMappedStatement(id,sqlSource,sqlCommandType,parameterTypeClass,resultMap,resultTypeClass,flushCache,useCache,keyGenerator,keyProperty,langDriver);// 给id加上namespace前缀id = builderAssistant.applyCurrentNamespace(id, false);// 存放键值生成器配置MappedStatement keyStatement = configuration.getMappedStatement(id);configuration.addKeyGenerator(id, new SelectKeyGenerator(keyStatement, executeBefore));}}
3.7.4 XML映射构建器
XMLMapperBuilder.java
package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.builder.MapperBuilderAssistant;
import com.lino.mybatis.builder.ResultMapResolver;
import com.lino.mybatis.cache.Cache;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.ResultFlag;
import com.lino.mybatis.mapping.ResultMap;
import com.lino.mybatis.mapping.ResultMapping;
import com.lino.mybatis.session.Configuration;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;/*** @description: XML映射构建器*/
public class XMLMapperBuilder extends BaseBuilder {.../*** 配置mapper元素* <mapper namespace="org.mybatis.example.BlogMapper">* <select id="selectBlog" parameterType="int" resultType="Blog">* select * from Blog where id = #{id}* </select>* </mapper>** @param element 元素*/private void configurationElement(Element element) {// 1.配置namespaceString namespace = element.attributeValue("namespace");if ("".equals(namespace)) {throw new RuntimeException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 2.配置cachecacheElement(element.element("cache"));// 3.解析resultMapresultMapElement(element.elements("resultMap"));// 4.配置select|insert|update|deletebuildStatementFromContext(element.elements("select"), element.elements("insert"), element.elements("update"), element.elements("delete"));}/*** <cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/>*/private void cacheElement(Element context) {if (context == null) {return;}// 基础配置信息String type = context.attributeValue("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);// 缓存队列 FIFOString eviction = context.attributeValue("eviction", "FIFO");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);Long flushInterval = Long.valueOf(context.attributeValue("flushInterval"));Integer size = Integer.valueOf(context.attributeValue("size"));boolean readWrite = !Boolean.parseBoolean(context.attributeValue("readOnly", "false"));boolean blocking = !Boolean.parseBoolean(context.attributeValue("blocking", "false"));// 解析额外属性信息:<property name="cacheFile" value="/tmp/xxx-cache.tmp"/>List<Element> elements = context.elements();Properties props = new Properties();for (Element element : elements) {props.setProperty(element.attributeValue("name"), element.attributeValue("value"));}// 构建缓存builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);}...
}
cacheElement
缓存标签的解析作用于configurationElement
解析环节中。解析后创建 Cache 存放到 Configuration 配置项对应的属性值。- 同时创建出来的缓存会被记录到 MappedStatement 映射语句类的属性上,便于在缓存执行器中使用。
3.7.5 XML配置构建器-全局缓存解析
<settings><!--全局缓存:true/false--><setting name="cacheEnabled" value="true"/><!--缓存级别:SESSION/STATEMENT--><setting name="localCacheScope" value="STATEMENT"/>
</settings>
- 在 Config XML 中配置全局缓存为开启,这个时候可以关闭一级缓存。
3.7.6 XML配置构建器
XMLConfigBuilder.java
package com.lino.mybatis.builder.xml;import com.lino.mybatis.builder.BaseBuilder;
import com.lino.mybatis.datasource.DataSourceFactory;
import com.lino.mybatis.io.Resources;
import com.lino.mybatis.mapping.Environment;
import com.lino.mybatis.plugin.Interceptor;
import com.lino.mybatis.session.Configuration;
import com.lino.mybatis.session.LocalCacheScope;
import com.lino.mybatis.transaction.TransactionFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.xml.sax.InputSource;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;
import java.util.Properties;/*** @description: XML配置构建器,建造者模式,集成BaseBuilder*/
public class XMLConfigBuilder extends BaseBuilder {.../*** <settings>* <!--缓存级别:SESSION/STATEMENT-->* <setting name="localCacheScope" value="SESSION"/>* </settings>*/private void settingElement(Element context) {if (context == null) {return;}List<Element> elements = context.elements();Properties props = new Properties();for (Element element : elements) {props.setProperty(element.attributeValue("name"), element.attributeValue("value"));}configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope")));}...}
- 解析全局配置的操作,再结合 XMLConfigBuilder 配置构建器中对配置解析扩展,添加
cacheEnabled
即可。 - 这样就可以把是否开启二级缓存的操作保存配置项目。
- 默认情况下,二级缓存是关闭的。
四、测试:二级缓存
4.1 修改配置
4.1.1 修改xml配置
mybatis-config-datasource.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>...<settings><!--全局缓存:true/false--><setting name="cacheEnabled" value="true"/><!--缓存级别:SESSION/STATEMENT--><setting name="localCacheScope" value="STATEMENT"/></settings>...
</configuration>
- 缓存的执行策略为二级缓存、一级缓存和数据库,所以这类的缓存配置可以根据代码测试阶段调整为一级、二级,交叉开启和关闭进行验证。
4.1.2 缓存策略配置
Activity_Mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lino.mybatis.test.dao.IActivityDao"><cache eviction="FIFO" flushInterval="600000" size="512" readOnly="true"/><resultMap id="activityMap" type="com.lino.mybatis.test.po.Activity"><id column="id" property="id"/><result column="activity_id" property="activityId"/><result column="activity_name" property="activityName"/><result column="activity_desc" property="activityDesc"/><result column="create_time" property="createTime"/><result column="update_time" property="updateTime"/></resultMap><select id="queryActivityById" parameterType="com.lino.mybatis.test.po.Activity" resultMap="activityMap">SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activity<trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and"><if test="null != activityId">activity_id = #{activityId}</if></trim></select></mapper>
cache
标签为二级缓存的使用策略,你可以配置FIFO、LRU
等不同的缓存策略。
4.2 单元测试
ApiTest.java
@Test
public void test_queryActivityById() throws IOException {// 1.从SqlSessionFactory中获取SqlSessionReader reader = Resources.getResourceAsReader("mybatis-config-datasource.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);// 2.请求对象Activity activity = new Activity();activity.setActivityId(100001L);// 3.第一组:SqlSession// 3.1 开启sessionSqlSession sqlSession01 = sqlSessionFactory.openSession();// 3.2 获取映射器对象IActivityDao dao01 = sqlSession01.getMapper(IActivityDao.class);logger.info("测试结果01:{}", JSON.toJSONString(dao01.queryActivityById(activity)));sqlSession01.close();// 4.第二组:SqlSession// 4.1 开启sessionSqlSession sqlSession02 = sqlSessionFactory.openSession();// 4.2 获取映射器对象IActivityDao dao02 = sqlSession02.getMapper(IActivityDao.class);logger.info("测试结果02:{}", JSON.toJSONString(dao02.queryActivityById(activity)));sqlSession02.close();
}
- 在单元测试中,分别两次开启
openSession
操作,并在第一次开启会话查询数据后执行会话关闭。- 因为实际代码实现,无论是
commit、close
结束会话,都会把一级缓存数据刷新到二级缓存,所以两个方式都可以。
- 因为实际代码实现,无论是
- 当会话关闭后开始执行第二次的会话开启,验证数据是否从二级缓存中获取数据,因为在二级缓存中添加了日志判断,所以可以通过打印日志进行验证。
测试结果
10:43:34.690 [main] INFO c.l.m.d.pooled.PooledDataSource - Created connention 899644639.
拦截SQL:SELECT activity_id, activity_name, activity_desc, create_time, update_timeFROM activitywhere activity_id = ?
10:43:34.700 [main] INFO c.l.m.s.d.DefaultParameterHandler - 根据每个ParameterMapping中的TypeHandler设置对应的参数信息 value:100001
10:43:34.700 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果01:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
10:43:34.700 [main] INFO c.l.m.d.pooled.PooledDataSource - Returned connection 899644639 to pool.
10:43:34.700 [main] INFO c.l.m.s.defaults.DefaultSqlSession - 执行查询 statement:com.lino.mybatis.test.dao.IActivityDao.queryActivityById parameter:{"activityId":100001}
10:43:34.700 [main] INFO c.l.mybatis.builder.SqlSourceBuilder - 构建参数映射 property:activityId propertyType:class java.lang.Long
10:43:34.700 [main] DEBUG c.lino.mybatis.executor.BaseExecutor - 二级缓存: [{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}]
10:43:34.700 [main] INFO com.lino.mybatis.test.ApiTest - 测试结果02:{"activityDesc":"测试活动","activityId":100001,"activityName":"活动名","createTime":1628424890000,"updateTime":1628424890000}
- 从断点调试和打印的日志看,此时框架已经可以在两次会话中完成数据的二次缓存使用。
五、总结:二级缓存
- 可以从二级缓存的设计实现上,看到 Mybatis 框架在这里运用了大量的装饰器模式,如 CachingExecutor 执行器和 Cache 接口的各类实现。
- 这样的设计的好处可以在不破坏原有逻辑的前提下,完成功能通过配置开关的自由开启使用。
- 虽然二级缓存在 Mybatis 框架中也是一个不错的设计,但由于系统架构逐步从单体到分布式以后,单个实例的数据缓存并没有太大的意义。
- 因为在分布式架构下,用户的每次请求会分散到不同应用实例上,那么单个缓存这个时候大概率就没法起到作用了。
- 所以在使用 Mybatis 时通常并不会开启二级缓存,而是使用 Redis 这样的框架来预热数据库数据使用。