为什么不推荐使用 MyBatis 二级缓存?
阅读本文大概需要 6 分钟。
来自:blog.csdn.net/xujingyiss/article/details/123481116
一级缓存
应用场景
member_id
字段查询出会员表,最后进行数据整合。而如果订单表中存在重复的member_id
,就会出现很多重复查询。生效的条件
必须是相同的会话 必须是同一个 mapper,即同一个 namespace 必须是相同的 statement,即同一个 mapper 中的同一个方法 必须是相同的 sql 和参数 查询语句中间没有执行 session.clearCache()
方法查询语句中间没有执行 insert/update/delete 方法(无论变动记录是否与缓存数据有无关系)
与springboot集成时一级缓存不生效原因
SqlSessionUtils
的 getSqlSession
方法,在这个方法中会尝试在事务管理器中获取 SqlSession,如果没有开启事务,那么就会 new 一个 DefaultSqlSession
。解决与springboot集成时一级缓存不生效问题
SqlSession
,取不到才会去创建新的 SqlSession
。所以可以猜测只要将方法开启事务,那么一级缓存就会生效。@Transactional
注解,看下效果:SqlSessionUtils
中,在获取到 SqlSession
后,会调用 registerSessionHolder
方法注册 SessionHolder
到事务管理器:TransactionSynchronizationManager
的 bindResource
方法中操作的,将 SessionHolder
保存到线程本地变量(ThreadLocal) resources
中,这是每个线程独享的。BaseExecutor
中的 queryFromDatabase
方法中。执行 doQuery 从数据库中查询数据后,会立马缓存到 localCache(PerpetualCache类型)
中:二级缓存
应用场景
@RestController
@RequestMapping("item")
public class ItemController {
@Autowired
private ItemMapper itemMapper;
@GetMapping("/{id}")
public void getById(@PathVariable("id") Long id) {
System.out.println("==================== begin ====================");
Item item = itemMapper.selectById(id);
System.out.println(JSON.toJSONString(item));
}
}
开启的方法
cache-enabled
为 truemybatis-plus:
configuration:
cache-enabled: true
@CacheNamespace
注解Serializable
接口生效的条件
当会话提交或关闭之后才会填充二级缓存 必须是同一个 mapper,即同一个命名空间 必须是相同的 statement,即同一个 mapper 中的同一个方法 必须是相同的 SQL 语句和参数 如果 readWrite=true
(默认就是true),实体对像必须实现Serializable
接口
缓存清除条件
只有修改会话提交之后,才会执行清空操作 xml 中配置的 update 不能清空 @CacheNamespace
中的缓存数据任何一种增删改操作都会清空整个 namespace
中的缓存
源码中是如何填充二级缓存的?
TransactionalCache
的 flushPendingEntries
方法中填充二级缓存:查询时如何使用二级缓存?
MybatisCachingExecutor
的 query 方法,里面会从 TransactionalCacheManager
中尝试根据 key 获取二级缓存的内容。PerpetualCache
中获取缓存的:LoggingCache
中:为什么mybatis默认不开启二级缓存?
namespace(mapper)
为单位的,不同 namespace 下的操作互不影响。且 insert/update/delete 操作会清空所在 namespace
下的全部缓存。ItemMapper
以及 XxxMapper
,在 XxxMapper
中做了表关联查询,且做了二级缓存。此时在 ItemMapper
中将 item 信息给删了,由于不同 namespace 下的操作互不影响,XxxMapper
的二级缓存不会变,那之后再次通过 XxxMapper
查询的数据就不对了,非常危险。@Mapper
@Repository
@CacheNamespace
public interface XxxMapper {
@Select("select i.id itemId,i.name itemName,p.amount,p.unit_price unitPrice " +
"from item i JOIN payment p on i.id = p.item_id where i.id = #{id}")
ListgetPaymentVO(Long id) ;
}
@Autowired
private XxxMapper xxxMapper;
@Test
void test() {
System.out.println("==================== 查询PaymentVO ====================");
ListvoList = xxxMapper.getPaymentVO(1L);
System.out.println(JSON.toJSONString(voList.get(0)));
System.out.println("==================== 更新item表的name ==================== ");
Item item = itemMapper.selectById(1);
item.setName("java并发编程");
itemMapper.updateById(item);
System.out.println("==================== 重新查询PaymentVO ==================== ");
ListvoList2 = xxxMapper.getPaymentVO(1L);
System.out.println(JSON.toJSONString(voList2.get(0)));
}
test()
方法中前后两次调用了 xxxMapper.getPaymentVO
方法,因为没有加 @Transactional
注解,所以前后两次查询,是两个不同的会话,第一次查询完后,SqlSession
会自动 commit,所以二级缓存能够生效;itemMapper
与 xxxMapper
不是同一个命名空间,所以 itemMapper
执行的更新操作不会影响到 xxxMapper
的二级缓存;xxxMapper.getPaymentVO
,发现取出的值是走缓存的,itemName
还是老的。但实际上 itemName
在上面已经被改了!推荐阅读:
互联网初中高级大厂面试题(9个G) 内容包含Java基础、JavaWeb、MySQL性能优化、JVM、锁、百万并发、消息队列、高性能缓存、反射、Spring全家桶原理、微服务、Zookeeper......等技术栈!
⬇戳阅读原文领取! 朕已阅