一、Mybatis的二级缓存机制(处理循环引用问题)
1.1一级缓存(局部缓存 Local Cache------HashMap对象)
一级缓存是默认开启且不能关闭,是SqlSession级别的缓存,在同个会话中,执行SQL后会将其存入缓存中,那自然下次再执行相同语句时,就从缓存中查询结果了。 一级缓存可以在配置文件中设置禁用或者刷新缓存来控制其使用
设置禁用:进行全局设置(setting)将localCacheScope属性设置为STATEMENT,表示每次SQL语句后都清空缓存
SqlSession提供了clearCache()来清空一级缓存
设置刷新:手动刷新:调用SqlSession对象的clearCache()方法清空一级缓存【仅能清空当前SqlSession的缓存】
在程序使用SqlSession执行增、删、改等DML语句是,或者提交事物,关闭SqlSession时,SqlSession缓存的所有数据都会被清空
1.2一级缓存的底层实现原理
1.mybatis执行select查询语句时,MyBatis将根据该select语句、参数生产key
2.MyBatis判断在一级缓存(HashMap对象)中是否能找到对于的key
3.如果能找到,则称之为“命中缓存”,MyBatis直接从一级缓存中获取该key对于的values并返回
4.如果没有命中缓存,则连接数据库执行select语句,得到查询结果,将key和查询结果作为key-value对放入一级缓存
5.判断缓存级别是否为STATEMENT,如果时的话,则清空一级缓存
1.3一级缓存产生脏数据和避免方法
如果将localCacheScope设为SESSION(默认值),那么每次查询都会存到一级缓存,这样就带来了风险:如果程序对这些返回值所引用的进行了修改---实际上就是修改一级缓存的对象,将会影响SqlSession生命周期内通过缓存所返回的值,从而产生脏数据
所以,不要对MyBatis返回的对象进行修改:当SqlSession执行DMML语句时,自动flush刷新缓存,
1.4对于一级缓存的建议
一级缓存是个粗粒度的缓存,没有缓存过期的概念,也没有容量的限定,因此不要让SqlSession长时间存活-------将SqlSession定义成局部变量,使用完之后立即关闭他
一级缓存声明周期与SqlSession一致,意味着一级缓存不可能跨SqlSession产生作用,在实际开发过程中,可能存在多条并发线程使用不同的SqlSession访问数据,比如一条线程的SqlSession读取数据,一条线程的SqlSession修改数据,就可能会导致脏数据的产生。
使用一级缓存的的最佳实践(实时性较高的话,避免使用一级缓存)
- 使用短生命周期的SqlSession
- 避免使用SqlSession一级缓存
但是一级缓存默认开启并不可关闭,这里有两种处理方式
1.每个SqlSession永远只执行单词查询,执行第二次查询就再打开另外一个SqlSession
2.将localCacheStore设置成STATEMENT,这样可避免在SqlSession范围内使用一级缓存,但是依旧有脏数据产生的可能。
二、二级缓存(默认关闭)
二级缓存时是基于命名空间的缓存,可用跨会话,在多个会话之间共享缓存,可以减少数据库的访问次数,要使用二级缓存,需要在MyBtais的配置文件中配置相应的缓存实现类,并在需要使用缓存的Mapper接口上添加@CacheNamespace注解。
二级缓存的默认清除算法时LRU(最近最少使用的先被清除)
缓存刷新间隔时间,默认不用刷新。
二级缓存开启后,同一个Mapper组建的多次查询语句相同并且参数相同时,只会执行一次-----后面将直接使用二级缓存查询数据。
二级缓存存在硬盘或者数据库中,因此被缓存的实体类应该实现Serializable接口,这样才能方便缓存机制将其序列化。
在代码中开启二级缓存,首先要将SqlSession关闭或者提交,这样才可以将数据写入二级缓存,并关闭一级缓存。
2.1二级缓存的问题
在不同Mapper之间并发访问时统一可能导致脏数据的产生
假设有PersonMapper里面有id=1的Person对象,关联的有id=2的Address对象
这时切换线程,使用AddressMapper修改Address对象中id=2的参数
这是切换回原线程,访问数据id=1的对象,在访问关联的id=2的对象,这时显示的还是修改前的数据
建议:有多表查询的场景,尽量不要用二级缓存