Mybatis 中一级缓存与二级缓存的区别

缓存:合理使用缓存是优化中最常见的方法之一,将从数据库中查询出来的数据放入缓存中,下次使用时不必从数据库查询,而是直接从缓存中读取,避免频繁操作数据库,减轻数据库的压力,同时提高系统性能。

  • 一级缓存是SqlSession级别的缓存,默认开启( 二级缓存默认不开启的 )
    Mybatis对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存。一级缓存在操作数据库时需要构造sqlSession对象,但和数据库操作有关的职责都会委托给Executor,通过BaseExecutor中的PerpetualCache localCache(底层就是一个Hashmap),。作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空. 不同的sqlSession之间的缓存数据区域是互相不影响的。也就是他只能作用在同一个sqlSession中,不同的sqlSession中的缓存是互相不能读取的。使用一级缓存。共有两个选项,SESSION或者STATEMENT默认是SESSION级别,即在一个MyBatis会话中执行的所有语句,都会共享这一个缓存。一种是STATEMENT级别,可以理解为缓存只对当前执行的这一个Statement有效。
  • 每个SqlSession中持有了Executor,每个Executor中有一个LocalCache(所以一级缓存是SqlSession级别的)。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement( eg: mapper.StudentMapper.getStudentById ),在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。

(1)一级缓存的生命周期有多长?

a、MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,每个SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

b、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

c、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

d、SqlSession中执行了任何一个update操作(update()、delete()、insert()) 并执行commit操作,都会清空PerpetualCache对象的数据,但是该对象可以继续使用 , 目的是避免脏读, 再次相同sql查询,会查询数据库,此时一级缓存失效 

 

延伸: 实际开发中,MyBatis通常和Spring进行整合开发。Spring将事务放到Service中管理,对于每一个service中的sqlsession是不同的,这是通过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer创建sqlsession自动注入到service中的。 每次查询之后都要进行关闭sqlSession(每次查询也会新建sqlsession),关闭之后数据被清空。所以spring整合之后,如果没有事务,一级缓存是没有意义的。

 

  • 除去hashcode、checksum和count的比较外,只要updatelist中的元素一一对应相等,那么就可以认为是CacheKey相等。只要两条SQL的下列五个值相同,即可以认为是相同的SQL,相同的sql回去map中查询是否存在。
  • Statement Id + Offset + Limmit + Sql + Params

一级缓存工作原理.png

<!--定义一级缓存级别  SESSION/STATEMENT STATEMENT不能实现sqlsession共享-->
<setting name="localCacheScope" value="SESSION"/>

 

 

  • 二级缓存是mapper级别的缓存:
    MyBatis的二级缓存是mapper级别(  Application级别的缓存,多个SqlSession可以共用二级缓存,SqlSession的 )的缓存,它可以提高对数据库查询的效率,以提高应用的性能。多个SqlSession去操作同一个Mapper的sql语句,。二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache, 当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库

二级缓存工作原理.png

开启二级缓存:
A.mybatis.xml配置文件中加入:

     <!--开启二级缓存-->    
    <setting name="cacheEnabled" value="true"/>   

B.在需要开启二级缓存的mapper.xml中加入caceh标签

<cache/>

C.让使用二级缓存的POJO类实现Serializable接口

public class User implements Serializable { }

如果我们配置了二级缓存就意味着:

(1)映射语句文件中的所有select语句将会被缓存
(2)映射语句文件中的所有insert、update和delete语句会刷新缓存
(3)缓存会使用默认的Least Recently Used(LRU,最近最少使用的)算法来收回。
(4)根据时间表,比如No Flush Interval,(CNFI没有刷新间隔),缓存不会以任何时间顺序来刷新。
(5)缓存会存储列表集合或对象(无论查询方法返回什么)的1024个引用
(6)缓存会被视为是read/write(可读/可写)的缓存,意味着对象检索不是共享的,而且可以安全的被调用者修改,不干扰其他调用者或线程所做的潜在修改。

总结

  1. MyBatis的二级缓存相对于一级缓存来说,实现了SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
  2. MyBatis在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
  3. 在分布式环境下,由于默认的MyBatis Cache实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将MyBatis的Cache接口实现,有一定的开发成本,直接使用Redis、Memcached等分布式缓存可能成本更低,安全性也更高。

     4.  通常我们会为每个单表创建单独的映射文件,由于MyBatis的二级缓存是基于namespace的,多表查询语句所在的namspace无法感应到其他namespace中的语句对多表查询中涉及的表进行的修改,引发脏数据问题。

@Test
public void testCacheWithCommitOrClose() throws Exception {
    SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
    SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务

    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);

    System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
    sqlSession1.commit();//此处提交事务 sqlsession2的查询,才会使用缓存,否则不会走缓存
    System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));

}
@Test
public void testCacheWithUpdate() throws Exception {
    SqlSession sqlSession1 = factory.openSession(true); // 自动提交事务
    SqlSession sqlSession2 = factory.openSession(true); // 自动提交事务
    SqlSession sqlSession3 = factory.openSession(true); // 自动提交事务


    StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
    StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
    StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);


    System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1));
    sqlSession1.close();
    System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));

    studentMapper3.updateStudentName("方方",1); //此处将会更新缓存
    sqlSession3.commit();
    System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1));
}

 参考:  https://tech.meituan.com/2018/01/19/mybatis-cache.html

          http://www.mybatis.cn/archives/77.html  

https://mp.weixin.qq.com/s?__biz=MzIyMDI5MzA3NQ==&mid=2247486407&idx=2&sn=5ebe8e1004d63a9a46bf0833120e0541&chksm=97cf797ba0b8f06d1a88100d2bdd687b398942c0d495cf52d0916a7b98d1af427ed54ead7db2&mpshare=1&scene=1&srcid=&sharer_sharetime=1582809587627&sharer_shareid=e1d4a35ff3308c63451b2ff386b1e2bd&key=9dbd9e970e4e89c3a44b81cf19d2072b1c2f9f39003761be21b49fe05f6b6603ad08422623f28582ea4a4e88dd8321c8846ce25fb41cae4de58bb22c6e3fb5ab76b5df5b63574f491d17e191b0417d75&ascene=1&uin=NjU1MzgyNTYw&devicetype=Windows+7&version=62080079&lang=zh_CN&exportkey=AXib3jXuweo2%2F9ULYNOkn1Y%3D&pass_ticket=NmhYqytrzUc%2BuTWrOffxJqOrIvPmBnzqFVx4skLxgd4g30weFYHrQzDIMfasL2U4

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值