mybatis缓存分为一级缓存和二级缓存,一级缓存作用当个sqlsession,如果sqlSession关闭了,那么缓存就失效了,二级缓存作用在mapper映射文件。默认二级缓存处理类是CachingExecutor,一级缓存处理类是BaseExecutor,调用query方法时会先从二级缓存中查找结果,再从一级缓存中找,都找不到再查询数据库。
mybatis缓存级别
一级缓存
作用域:当个sqlsession,仅仅对一个会话中的数据进行缓存,不能关闭,调用query查询时,将查询结果缓存在BaseExecutor.localCache变量中,BaseExecutor对象随sqlSession创建而创建,当sqlSession销毁时,BaseExecutor随之消失,缓存消失。
sqlSession从open()到close()中间整个范围就是单个sqlSession作用域,作用域区间如果调用相同的查询方法,并且参数相同,那么只会执行一次sql。
//打开一个sqlsession
SqlSession session = ssf.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap();
map.put("name","loli");
List<User> list = userMapper.selectByMap(map);
List<User> list2 = userMapper.selectByMap(map);
//关闭sqlsession
session.close();
二级缓存
作用域:跨sqlsession,全局sqlSession,也叫映射器缓存,默认开启,可管理开关,通过mybatis系统配置可以设置,默认为true,但是还需在每个mapper.xml映射文件中添加<cache>标签开启当前mapper映射文件的二级缓存。
<configuration >
......
<settings>
<!--全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为true。-->
<setting name="cacheEnabled" value="true"></setting>
</settings>
......
</configuration>
在解析mapper.xml时,会读取<cache>节点配置,如果没有配置,则当前mapper不开启二级缓存,否则开启二级缓存。
根据<cache>节点配置封装Cache对象,id为namespace值,所以一个mapper.xml虽然有多个<select><delete>等sql,但是每个sql封装的MappedStatement对象引用的是同一个cache变量。相当于同一mapper.xml文件下sql封装的MappedStatement对象,引用同一个cache对象。
<cache>节点配置属性介绍请详见官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
<mapper namespace="com.lu.UserMapper" >
<cache
type="com.lu.xxxxCache"
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"
blocking="true" />
</mapper>
所以二级缓存跟sqlsession没有关系,不会随着sqlsession销毁而清除,属于全局缓存,不同sqlsession调用同一个mapper,同一个sql,同样传参,sql只会执行一遍。
例子:如果UserMapper.xml开启了二级缓存,虽然open了两个sqlsession,但是调用相同的UserMapper.xml,相同selectByMap查询方法,相同map参数,所以也只会执行一遍sql。
SqlSession session1 = ssf.openSession();
SqlSession session2 = ssf.openSession();
UserMapper userMapper1 = session1.getMapper(UserMapper.class);
UserMapper userMapper2 = session2.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap();
map.put("name","loli");
List<User> list1 = userMapper1.selectByMap(map);
List<User> list2 = userMapper2.selectByMap(map);
session1.close();
session2.close();
缓存执行流程图
一级缓存
缓存对象初始化
缓存对象PerpetualCache,缓存其实就是将查询结果存在PerpetualCache类中Map cache属性中。
代码分析:
所有缓存对象存在cache map中,map的key表示查询SQL封装的CacheKey对象,value表示查询结果。
public class PerpetualCache implements Cache {
private final String id;
//map存储cache对象
private Map<Object, Object> cache = new HashMap<>();
.......
}
PerpetualCache localCache对象在什么时候初始化的呢?
PerpetualCache localCache是在调用openSession方法生成sqlsession时初始化。
SqlSession session = ssf.openSession();
org.apache.ibatis.session.Configuration#newExecutor
这些Executor类都继承自BaseExecutor基类,在new Executor对象时,会初始化BaseExecutor对象,BaseExecutor对象包含PerpetualCache localCache属性,此时就会初始化localCache一级缓存对象。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 默认执行器 SIMPLE,可配置defaultExecutorType指定执行器
// SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
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);
}
//二级缓存开关,默认开启true
//如果开启二级缓存,则封装executor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
//封装自定义的Executor插件类
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
org.apache.ibatis.executor.BaseExecutor#BaseExecutor
public abstract class BaseExecutor implements Executor {
protected PerpetualCache localCache;
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;
}
}
缓存使用
执行insert/delete/update操作
会先清除缓存,在执行增删改操作。
代码分析:
org.apache.ibatis.session.defaults.DefaultSqlSession#insert(java.lang.String, java.lang.Object)
/**
* 插入
* @return
*/
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
/**
* 更新操作
* @return
*/
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
org.apache.ibatis.executor.BaseExecutor#update
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//清除一级缓存
clearLocalCache();
return doUpdate(ms, parameter);
}
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
执行query查询操作
执行查询操作时,步骤如下
- 构建CacheKey key对象
- 调用localCache.getObject(key)从缓存中获取查询结果,如果存在则直接返回结果。
- 如果缓存中没有匹配key,调用localCache.putObject(key, EXECUTION_PLACEHOLDER)方法在一级缓存占位,再查询数据库得到结果,移除缓存中存的占位数据
- 调用localCache.putObject(key, list)缓存查询结果
代码分析 :
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object)
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据key=statement=com.lu.UserMapper.selectByMap,从configuration对象mappedStatements(map)中获取MappedStatement对象,
// MappedStatement对象封装了<select id="selectByMap">所有配置信息
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
org.apache.ibatis.executor.BaseExecutor#query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//如果sql是动态sql,如<if> ${id}标签会被替换成参数,如果是#{}则在后面才会替换参数
BoundSql boundSql = ms.getBoundSql(parameterObject);
//new一个缓存对象,ms必须是同一个,参数相同,分页相同,sql相同
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@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++;
//localCache一级缓存,判断缓存中是否存在
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//<select statementType="CALLABLE">类型,是否执行存储过程
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;
}
/**
* 查询数据库
* @return
* @throws SQLException
*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//在一级缓存调用put缓存占位
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;
}
二级缓存
缓存对象初始化
默认缓存对象PerpetualCache,缓存其实就是将查询结果存在PerpetualCache类中Map cache属性中,也可自己指定其他缓存实现类。
二级缓存对象和一级缓存对象一样,但是二级缓存采用包装者模式封装了PerpetualCache对象。
PerpetualCache localCache
在什么时候初始化对象的呢?
解析mapper映射文件<cache>节点配置,会初始化cache缓存对象,并采用包装者模式封装PerpetualCache缓存对象。
<cache>节点配置属性介绍请详见官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
<mapper namespace="com.lu.UserMapper" >
<cache
type="com.lu.xxxxCache"
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"
blocking="true" />
</mapper>
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement
/**
* 二级缓存配置封装对象
* @param context
*/
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//根据配置参数构建cache缓存对象
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache
读取<cache>节点配置,给CacheBuilder属性赋值。调用build()方法包装cache对象。
/**
* 构建cache包装类
* @param typeClass cache实现类,可自定义实现类
* @param evictionClass 清除缓存策略 LRU FIFO SOFT WEAK
* @param flushInterval 刷新缓存间隔时间
* @param size 缓存个数 默认1024
* @param readWrite 是否需要读写,如果为true,就需要序列化缓存数据
* @param blocking 读取缓存是否需要上锁
* @param props 缓存相关配置信息
* @return
*/
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();//构建cache包装类
//保存缓存
configuration.addCache(cache);
currentCache = cache;
return cache;
}
org.apache.ibatis.mapping.CacheBuilder#build
这里就是解析是否指定缓存实现类,默认底层缓存实现类是PerpetualCache,
如果使用默认实现类PerpetualCache,则会采用装饰着封装PerpetualCache对象。
如果自定义了缓存实现类,则只会在自定义类上装饰一层LoggingCache对象。
public Cache build() {
//判断<cache type="class">是否自定义缓存实现类,
// 如果type为空,则使用implementation=PerpetualCache默认实现类,否则implementation=自定义类
setDefaultImplementations();
//根据implementation变量,反射构造cache对象
Cache cache = newBaseCacheInstance(implementation, id);
//<cache>
// <property name="" value=""></property>
//</cache>
//property赋值给cache对象
setCacheProperties(cache);
// issue #352, do not apply decorators to custom caches
//如果二级缓存实现类是默认的PerpetualCache类,则使用mybatis默认的包装流程
//否则直接使用自定义cache实现类
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
//包装cache对象
cache = setStandardDecorators(cache);
//判断自定义缓存实现类是否继承自LoggingCache,不是,则LoggingCache(cache)包装
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
cache = new LoggingCache(cache);
}
return cache;
}
org.apache.ibatis.mapping.CacheBuilder#setStandardDecorators
装饰者封装了多层cache
- cache=new PerpetualCache(cache) 缓存真正实现
- cache=new ScheduledCache(cache) 定义缓存清除时间t1,在操作缓存时,判断当前时间-最后操作缓存时间,如果大于t1,则清除缓存
- cache=new SerializedCache(cache) 序列化缓存对象
- cache=new LoggingCache(cache) 记录缓存命中率日志
- cache=new SynchronizedCache(cache) 所有操作缓存方法上加synchronized关键字
- cache=new BlockingCache(cache) 使用ReentrantLock操作锁代码
/**
* 封装cache对象
* cache=ScheduledCache(cache)
* cache=SerializedCache(cache)
* cache=LoggingCache(cache)
* cache=SynchronizedCache(cache)
* cache=BlockingCache(cache)
* @param cache
* @return
*/
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
//<cache flushInterval="1000">配置
//ScheduledCache不会定时删除缓存,每次get/put等操作缓存时
// if( 当前系统时间 - 上一次清除时间 > flushInterval时间 ) 则清除缓存,并设置清除时间=系统时间
// else 向下传递
if (clearInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
//<cache readOnly="false">配置
//序列化缓存对象
if (readWrite) {
cache = new SerializedCache(cache);
}
//记录日志
cache = new LoggingCache(cache);
//同步缓存操作
cache = new SynchronizedCache(cache);
//<cache blocking="true">配置
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
缓存使用
执行insert/delete/update操作
默认先清除相应mapper上的缓存,在执行增删改操作。
代码分析:
org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String, java.lang.Object)
org.apache.ibatis.executor.CachingExecutor#update
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//清除缓存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
/**
* 清除缓存
* @param ms
*/
private void flushCacheIfRequired(MappedStatement ms) {
//获取mapper映射器缓存对象
Cache cache = ms.getCache();
//根据sql节点上flushCache值,判断是否刷新缓存,默认:增删改true,查询false
if (cache != null && ms.isFlushCacheRequired()) {
//清除缓存
tcm.clear(cache);
}
}
执行query查询操作
执行查询操作,缓存调用过程大致如下:
- 创建CacheKey key缓存对象
- ms.getCache()获取mapper映射文件对应的二级缓存对象(经过多层包装的cache对象)
- flushCacheIfRequired(ms)判断是否要清除mapper的二级缓存,逻辑同一级缓存一样,根据<select flushCache="true">节点上flushCache值判断
- ms.isUseCache()判断当前sql是否需要缓存,根据<select useCache="true">useCache值判断,为true表示使用缓存,否则不缓存
- tcm.getObject()查询事务管理缓存tcm,存在则直接缓存,不存在查询数据库,将结果缓存在事务管理缓存tcm中
- 调用commit()方法,将事务管理缓存tcm中缓存值保存在真正缓存对象中。
代码分析:
org.apache.ibatis.executor.CachingExecutor#query
/**
* 二级缓存
*/
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//如果sql是动态sql,如<if> ${id}标签会被替换成参数,如果是#{}则在后面才会替换参数
BoundSql boundSql = ms.getBoundSql(parameterObject);
//new一个缓存对象,ms必须是同一个,参数相同,分页相同,sql相同
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 {
//二级缓存对象,在解析build mapper.xml中<cache>标签时构建的cache对象
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;
}
}
//不使用二级缓存,delegate实现类是SimpleExecutor类,delegate在Test1 main方法中获取sqlSession时赋值的
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
事物缓存
上面使用二级缓存时,查询结果是先存到事务管理缓存tcm,而不是直接存储在PerpetualCach cache底层缓存对象中,是因为mybatis存在事物管理。
将查询结果直接缓存在PerpetualCach cache底层map中不可以么?
事物缓存管理是为了防止出现如下情况,下图操作失败,插入的a数据在数据库中会回滚,但是二级缓存已经缓存了结果a,这种操作是不正确的。
那么mybatis是如何实现的呢?
我们看下mybatis事物缓存是如何实现的,回顾CachingExecutor类查询操作中缓存实现
org.apache.ibatis.executor.CachingExecutor#query
//管理事物缓存
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//二级缓存对象,在解析build mapper.xml中<cache>标签时构建的cache对象
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;
}
}
//不使用二级缓存,delegate实现类是SimpleExecutor类,delegate在Test1 main方法中获取sqlSession时赋值的
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
TransactionalCacheManager类是用来管理事物缓存的,有commit和rollback两个方法,但是底层实现是TransactionCache类。
public class TransactionalCacheManager {
//key=mapper对应的cache对象
//value=缓存当前事物中,对key中mapper所有查询sql及结果封装的TransactionalCache对象
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
/**
* 调用TransactionalCache.clear()方法
* 清除当前事物中待提交的缓存记录
* @param cache
*/
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
/**
* 调用TransactionalCache.getObject()方法
* 根据key获取缓存value结果
* @param cache
* @param key
* @return
*/
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
/**
* 调用TransactionalCache.getObject()方法
* 将key,value缓存到TransactionalCache#entriesToAddOnCommit待提交缓存map属性中
* @param cache
* @param key
* @param value
*/
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
/**
* 提交事务
*/
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
/**
* 事物回滚
*/
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
/**
* transactionalCaches根据key获取value
* 如果不存在则new TransactionalCache(PerpetualCach cache)
* @param cache
* @return
*/
private TransactionalCache getTransactionalCache(Cache cache) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
TransactionalCache类有两个重要的属性entriesToAddOnCommit,entriesMissedInCache,在事物中查询的结果会缓存到这两个属性中,
- entriesToAddOnCommit:保存待提交的缓存
- entriesMissedInCache:未命中缓存集合,防止缓存穿透(防止一直访问数据库中没有的数据,导致一直查询缓存和数据库
在事物未提交前,会将当前事物中所有查询结果临时存到entriesToAddOnCommit,entriesMissedInCache属性map中。
事物提交成功后会调用commit()方法,将所有临时缓存对象保存到底层真正PerpetualCach cache属性map中。
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private final Cache delegate;
private boolean clearOnCommit;
//待提交的缓存
private final Map<Object, Object> entriesToAddOnCommit;
//未命中缓存集合,防止缓存穿透(防止一直访问数据库中没有的数据,导致一直查询缓存和数据库)
private final Set<Object> entriesMissedInCache;
/**
* 查询缓存
* @param key The key
* @return
*/
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
/**
* 将缓存对象临时保存在entriesToAddOnCommit属性map中
* @param key cache缓存对象,包含查询sql,参数,分页等
* @param object 查询结果
*/
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
/**
* 提交缓存
*/
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
//刷新缓存,将待提交的缓存put到真实缓存map对象中
flushPendingEntries();
//重置清空属性值
reset();
}
/**
* 回滚
* 删除未命中的缓存
*
*/
public void rollback() {
unlockMissedEntries();
reset();
}
/**
* 清除属性值
*/
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
/**
* 刷新缓存
* 将待提交的缓存entriesToAddOnCommit中值保存到底层真正存储缓存PerpetualCach cache属性map中
* 将未命中的缓存entriesMissedInCache中值保存,设置结果设置为null
*/
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
//put到真正缓存中
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
//把未命中的put到
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
/**
* 将未命中的缓存从底层缓存map中删除
*/
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
缓存对象
如何判断缓存存在,根据什么条件判断是查询同一个sql,判断namespace,还是sql语句?
判断代码如下,key表示CacheKey类
public class PerpetualCache implements Cache {
//map存储cache对象
private Map<Object, Object> cache = new HashMap<>();
public Object getObject(Object key) {
return cache.get(key);
}
}
缓存对象判断逻辑代码分析:
org.apache.ibatis.executor.BaseExecutor#query
执行查询方法时,通过调用query方法,执行createCacheKey()创建cache对象。
调用cacheKey.update()方法,传入重要参数MappedStatement id,sql,分页,参数值,保存到CacheKey类updateList属性中。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//动态sql参数赋值
BoundSql boundSql = ms.getBoundSql(parameter);
//创建cache缓存对象
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
//mapper id+sql id 全名
//com.lu.UserMapper.selectByMap
cacheKey.update(ms.getId());
//分页参数
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
//查询sql,动态sql ${}替换成参数,#{}为?
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;
}
CacheKey类,通过调用重写的equals()方法,循环判断updateList中值是否相等,来判断是否是同一个缓存对象。
public class CacheKey implements Cloneable, Serializable {
.......
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
if (hashcode != cacheKey.hashcode) {
return false;
}
if (checksum != cacheKey.checksum) {
return false;
}
if (count != cacheKey.count) {
return false;
}
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
来源:oschina
链接:https://my.oschina.net/u/2264370/blog/3112135