加上flushCache=”true”后,再次运行结果如下
2.二级缓存
mybatis的二级缓存默认开启,但真正使用需要在mapper文件中添加相应的缓存配置
二级缓存存在于SqlSessionFactory生命周期中, 每个二级缓存对同一个mapper文件中的SELECT操作有效
#Configuration
protected boolean cacheEnabled = true;
// 执行器默认会传入到CachingExecutor进行一层包装
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
#CacheExecutor
// delegate委托对象 为上述传入的一种具体的执行器
// cacheExecutor的具体查询更新操作都是通过委托对象来进行操作的
private final Executor delegate;
// 缓存管理器
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
// 具体分析下cacheExecutor的query方法
@Override
public <e> List<e> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 看是否有配置二级缓存
Cache cache = ms.getCache();
if (cache != null) {
// 根据cache != null && ms.isFlushCacheRequired() 刷新二级缓存
// 若select标签中没有配置 flushCache="true"则不会刷新
flushCacheIfRequired(ms);
// 是否将查询结果进行二级缓存 select标签默认是true 也可用useCache="false"配置不进行缓存
if (ms.isUseCache() && resultHandler == null) {
// 存储过程相关校验
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 根据cacheKey从缓存中取数据
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;
}
}
// 若没有配置二级缓存 实际上就直接执行BaseExecutor中的query方法
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}</e></e></e></e>
// 上述tcm.putObject(cache, key, list);
实际上调用了TransactionalCache的putObject方法,将结果放进entriesToAddOnCommit这个map中,并没有缓存到delegate中
只有调用sqlSession的commit或close方法 才会将结果存进缓存(通过flushPendingEntries方法)
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
// 委托对象 真实的缓存 最后结果将存进这个缓存
private final Cache delegate;
// 若为true,则commit的时候会将delegate清空
private boolean clearOnCommit;
// 存储commit时需要放进缓存的对象
private final Map<object, object> entriesToAddOnCommit;
// 存储调用getObject()获取不到的key
private final Set<object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@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;
}
}
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
public void rollback() {
unlockMissedEntries();
reset();
}
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
private void flushPendingEntries() {
// 遍历entriesToAddOnCommit,将结果缓存
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) {
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);
}
}
}
}</object,></object></object,>
测试代码
public void testCache2() {
SqlSession sqlSession = null;
try {
// 开启一个sqlSession
sqlSession = sqlSessionFactory.openSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student1 = studentMapper.getStudentById(1);
sqlSession.close();
log.info("开启新的session");
sqlSession = sqlSessionFactory.openSession();
studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student2 = studentMapper.getStudentById(1);
Student student3 = studentMapper.getStudentById(1);
Student student4 = studentMapper.getStudentById(1);
log.info("student1 == student2, result:" + (student1 == student2));
log.info("student1 == student3, result:" + (student1 == student3));
log.info("student1 == student4, result:" + (student1 == student4));
} finally {
sqlSession.close();
}
}
结果输出
可以看到后三次的查询都命中了缓存,没有再去查找数据库。另外,查询出来的结果都不是同一个对象,事实上缓存的时候SerializedCache这个缓存 ,所有二级缓存的对象需要实现Serializable接口,后面存储的其实是对象的字节数组,这样反序列化出来的时候就不是同一个对象了。
若某个查询不想使用二级缓存,也可以加上flushCache=”true”这个配置,同一级缓存一样
#SerializedCache
@Override
public void putObject(Object key, Object object) {
if (object == null || object instanceof Serializable) {
delegate.putObject(key, serialize((Serializable) object));
} else {
throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
}
}
private byte[] serialize(Serializable value) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(value);
oos.flush();
return bos.toByteArray();
} catch (Exception e) {
throw new CacheException("Error serializing object. Cause: " + e, e);
}
}
最后
无论是一级缓存还是二级缓存再调用update()方法后都会clear清空缓存,使缓存失效。实际开发使用中mybatis缓存的坑还是很多的,需要谨慎小心。
Original: https://www.cnblogs.com/monianxd/p/16455915.html
Author: 默念x
Title: mybatis缓存
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/620920/
转载文章受原作者版权保护。转载请注明原作者出处!