查询缓存
mybaits提供了一级缓存和二级缓存,用于减轻数据压力,提高数据库性能。。
1.1 什么是一级缓存
我们创建出一个SqlSession对象表示一次数据库会话,在这次会话中,我们有可能好几次执行一样的查询语句,若是每次都去访问数据库,那么就会造成数据库资源的浪费,所以mybatis引入一级缓存,在SqlSession中建立一个缓存(数据结构为hashmap),每次查询先去查看缓存中是否有,有的话就把结果返回,没有的话再去查询数据库并将查到的结果缓存起来。
需要注意的是,不同的sqlSession对象之间的缓存数据区域是互相不影响的。
SqlSession只是对外的接口,真正执行各种数据库操作的是Executor执行器(也是一个接口)。当创建了一个SqlSession对象时,MyBatis会为这个SqlSession对象创建一个新的Executor执行器,而缓存信息就被维护在这个Executor执行器中。MyBatis将缓存和对缓存相关的操作封装成了Cache接口中。
SqlSession、Executor、Cache之间的关系如下列类图所示:
sqlSession级别的一级缓存实际上就是使用PerpetualCache维护的。
1.2一级缓存的生命周期
开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉;
如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,此时一级缓存不可用;
如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是PerpetualCache对象仍可使用;
4.SqlSession中执行了一个更新增加删除操作 ,都会清空全部PerpetualCache对象的数据,但是该对象可以继续使用;
1.3 一级缓存的工作流程
对于某个查询,根据statementId,params,rowBounds来构建一个key值;
根据这个key值去缓存Cache中判断是否命中缓存;
如果命中,则直接将缓存结果返回;
-
如果没命中:
4.1 去数据库中查询数据,得到查询结果; 4.2 将key和查询到的结果分别作为key,value对存储到Cache中; 4.3. 将查询结果返回;
结束。
1.4 如何判断两次查询是一样的
从实现来看,mybatis是利用key是否相同来判断两次查询是否相同的,而key是由statementId,params,rowBounds这三项构建的。
其中,statementId决定带参数的sql(有“?”占位符的的sql或是需要拼接字符串的sql语句),params决定占位符传入的参数或是要拼接的字符串,rowBounds决定查询的结果集的范围(也就是 limit x,y ),这三个参数决定了最终的sql,也就是说当这个最终的sql一样时,那么就判断这两次是一样查询。
1.5 真正应用
真正开发的时候,sqlSession是用spring管理的,而mapper是在service里面调用,在这种情况下,什么时候会用到一级缓存呢?
实际上,在一个service里,只有一个sqlSession,在service结束之后才会关闭这个sqlSession,sqlSession关闭则缓存清空。所以一个service函数里面多次调用mapper是会走一级缓存的,而多次调用service则互不影响。
2.1 什么是二级缓存
二级缓存是namespace级别的缓存,多个SqlSession可以共用二级缓存,不同sqlSession操作同一个namespace下的同一个sql,就会走二级缓存。
2.2开启二级缓存
在SqlMapConfig.xml里开启mybatis的全局二级缓存开关,默认是开启的:
还需要在要二级缓存开启的mapper.xml里面开启开关:
2.3 二级缓存与一级缓存区别
二级缓存与一级缓存区别,二级缓存的范围更大,多个sqlSession可以共享一个namespace下的二级缓存区域。
2.4 测试二级缓存
需要关闭sqlSession1才会把内容写到缓存,sqlSession2才会读到:
在sqlSession1和sqlSession2之间加入以下代码,为了测试更新操作是否清空缓存:
2.5 二级缓存参数设置
设置某些sql禁用二级缓存
在statement中设置useCache=false可以禁用当前select语句使用二级缓存,即每次查询都会发出sql去查询数据库。默认情况是true,即该sql使用二级缓存。
只有每次查询都需要最新的数据sql,要设置成useCache=false,才禁用二级缓存。
刷新缓存操作
刷新就是清空缓存,在insert update delete等statement中设置flushCache="true"(默认就是true),进行这些操作后会清空缓存,可以避免脏读的情况。
刷新间隔参数
默认是没有刷新间隔,刷新只在调用语句时刷新(这里的刷新都是指清空)
引用数目属性
size:可被设置成任意正整数,默认值是1024
readOnly属性
可被设置成true或是false,默认是false。只读为true的缓存的对象实例是同一个;只读为false的缓存返回的对象实例是拷贝的(通过序列化,所以配置成false需要pojo实现序列化接口),慢但是安全。
一个设置参数的例子
这个配置了一个FIFO缓存;并每隔60秒(60 000ms)刷新;存储结果对象或结果列表的引用数是512个;返回的结果是只读的,因此在不同线程中调用修改它们会冲突。可用的回收策略默认是LRU。
2.6 二级缓存应用场景
单表查询时,对于实时性要求不高的请求,可以采用二级缓存并设置缓存刷新间隔来减少数据库访问量,提高访问速度。
2.7 mybatis二级缓存局限性
最致命的是,多表查询的情况出现的问题,比如我做订单和用户的多表查询,这个statement可以写在UserMapper.xml或是OrderMapper.xml或者是另创一个xml,假设我把这个statement放进OrderMapper.xml,我多表查询了一次,结果被缓存下来,那么这时候我再去修改user的信息,可是这个更新操作是UserMapper.xml,我们使用mybatis的逆向工程,这两个xml的namespace是不一样的,也就是说这次更新操作没有清空OrderMapper的缓存,那么我下次多表查询就是查到错误的数据。
解决方法:
可以加入
配置,cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。
也可以是设计一个拦截器,判断涉及到的表,进行更新操作,把有关的表的缓存都清空,不过这样效率有点低。
mybatis的二级缓存默认是本地的,分布式环境下可能会有过时缓存数据被读取的情况,所以还是放弃mybatis的二级缓存,选择在业务层引入可控制的缓存替代比较好,比如redis等等。
3. ehcache
ehcache可以对页面、对象、数据进行缓存,同时支持集群/分布式缓存。
怎么整合mybatis
在核心配置文件SqlMapConfig.xml开启mybatis的二级缓存,默认是开启的
导入ehcache相关jar包
- ehcache-core-2.6.5.jar
- mybatis-ehcache-1.0.2.jar
-
在classpath下加入ehcache.xml文件
-
在XXXMapper.xml中开启二缓存,XXXMapper.xml下的sql执行完成会存储到它的缓存区域(HashMap)