MyBatis的一级(同SqlSession会话),二级(不同SqlSession会话)缓存使用

1:一级缓存要生效,必须满足以下条件条件

  • 必须是相同的会话
  • 必须是同一个 mapper,即同一个 namespace
  • 必须是相同的 statement,即同一个 mapper 中的同一个方法
  • 必须是相同的 SQL 和参数
  • 查询语句中间没有执行 session.clearCache() 方法
  • 查询语句中间没有执行 insert/update/delete 方法

2:与 SpringBoot 集成时一级缓存不生效原因

  • 因为一级缓存是会话级别的,要生效的话,必须要在同一个 SqlSession 中。但是与 SpringBoot 集成的 MyBatis,默认每次执行 SQL 语句时,都会创建一个新的 SqlSession!所以一级缓存才没有生效。
  • 当调用 mapper 的方法时,最终会执行到 SqlSessionUtils 的 getSqlSession 方法,在这个方法中会尝试在事务管理器中获取 SqlSession,如果没有开启事务,那么就会 new 一个 DefaultSqlSession。所以说,即便在同一个方法中,通过同一个 mapper 连续调用两次相同的查询方法,也不会触发一级缓存

3:解决与 SpringBoot 集成时一级缓存不生效问题

只要将方法加上 @Transactional 注解开启事务,那么一级缓存就会生效

4:mybatis为什么要有二级缓存

  • 业务系统中存在很多的静态数据如,字典表、菜单表、权限表等,这些数据的特性是不会轻易修改但又是查询的热点数据
  • 一级缓存针对的是同一个会话当中相同 SQL,并不适合热点数据的缓存场景
  • 为了解决这个问题引入了二级缓存,它脱离于会话之外,多个会话可以使用相同的缓存

5:配置启动mybatis有二级缓存

  • 二级缓存需要手动来开启,MyBatis 默认没有开启二级缓存
  • 在 yaml 中配置 cache-enabled 为 true
mybatis-plus:
  configuration:
    cache-enabled: true
  • 实体类实现 Serializable 接口和生成SerialVersionUID
public class SysNotification implements Serializable {
    
    
	private static final long serialVersionUID = 3470806756562029897L;
  • 测试当发送两次 get 请求时(两个不同的会话),通过日志可以发现第二次查询使用的是缓存
Cache Hit Ratio [org.jeecg.modules.system.mapper.SysNotificationMapper]: 0.5

生效的条件:

  • 当会话提交或关闭之后才会填充二级缓存
  • 必须是同一个 mapper,即同一个命名空间
  • 必须是相同的 statement,即同一个 mapper 中的同一个方法
  • 必须是相同的 SQL 语句和参数
  • 如果 readWrite=true(默认就是true),实体对像必须实现 Serializable 接口

失效的条件:

  • 只有修改会话提交之后,才会执行清空操作
  • 任何一种增删改操作都会清空整个 namespace 中的缓存

6:为什么 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}")
    List<PaymentVO> getPaymentVO(Long id);

}


@Autowired
private XxxMapper xxxMapper;

@Test
void test() {
    
    
  System.out.println("==================== 查询PaymentVO ====================");
  List<PaymentVO> voList = 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 ==================== ");
  List<PaymentVO> voList2 = xxxMapper.getPaymentVO(1L);
  System.out.println(JSON.toJSONString(voList2.get(0)));
}
  • 上面的代码,test() 方法中前后两次调用了 xxxMapper.getPaymentVO 方法,因为没有加 @Transactional 注解,所以前后两次查询,是两个不同的会话,第一次查询完后,SqlSession 会自动 commit,所以二级缓存能够生效;
  • 然后在中间进行了 Item 表的更新操作,修改了下名称;
  • 由于 itemMapper 与 xxxMapper 不是同一个命名空间,所以 itemMapper 执行的更新操作不会影响到 xxxMapper 的二级缓存;
    再次调用 xxxMapper.getPaymentVO,发现取出的值是走缓存的,itemName 还是老的。但实际上 itemName 在上面已经被改了!
  • 所以说,二级缓存的隐藏危害是比较大的,当有表关联时,一个不注意就会出问题,不建议使用

7:可以使用Spring缓存注解@Cacheable、@CacheEvict、@CachePut

猜你喜欢

转载自blog.csdn.net/qq_19891197/article/details/131460812