为什么不推荐使用 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......等技术栈!
⬇戳阅读原文领取! 朕已阅

