查询缓存
查询缓存的使用,主要是为了提高查询访问速度。将用户对同一数据的重复查询过程简化,不再每次均从数据库查询获取结果数据,从而提高访问速度。
左为执行原理示意图 右为执行流程图
作用域:mybatis查询缓存的作用域是根据映射文件mapper的namespace划分的,相同namespace的mapper查询数据存放在同一缓存区域。不同namespace下的数据互不干扰。无论是一级缓存还是二级缓存,都是按照namespace进行分别存放的。
生命周期:但一、二级缓存的不同之处在于,SqlSession一旦关闭,则SqlSession中的数据将不存在,即一级缓存就不复存在。而二级缓存的生命周期会与整个应用同步,与SqlSession是否关闭无关。换句话说,一级缓存是在同一线程(同一SqlSession)间共 享数据,而二级缓存是在不同线程(不同的SqlSession)间共享数据。 不同的SqlSession共享数据也是在同一个namespace下。
1.一级缓存
- mybatis默认一级缓存是开启状态,且不能关闭。
- 证明一级缓存的存在
证明一级缓存是存在的 只执行了一次sq
- 从缓存中查找数据的依据
缓存的底层实现是一个Map,Map的value是查询结果
Map的key,即查询依据,使用的ORM框架不同,查询依据是不同的。
Mybatis 查询的依据是:Sql的id + SQL语句 + 哈希值 这里的用是select 的 id(selectStudentByid 和 selectStudentByid2) 和 sql语句不同传的值为 2 或者 3
Hibernate 的查询依据是:查询结果对象的id
- 增删改对一级缓存的影响
增删改操作都会清空一级缓存,无论是否提交
为什么要把缓存清空了?
比如要查询年龄大于20的,下次查询就要刷新了,不然数据会出错。
2.内置二级缓存
由于MyBatis从缓存中读取数据的依据与SQL的id相关,而非查询出的对象。所以,使用二级缓存的目的,不是在多个查询间共享查询结果(所有查询中只要查询结果中存在该对象的,就直接从缓存中读取,这是对查询结果的共 享,Hibernate中的缓存就是为了在多个查询间共享查询结果,但MyBatis的不是),而是为了防止同一查询(相同的Sql id、相同的Sql语句)的反复执行。
Hibernate只要查询了AllStudents 那么在下次查询某个学生就可以从缓存中找了。
- 二级缓存的开启:在映射文件里加入<cache/> 同时 将实体类序列化
- 证明内置二级缓存的存在
只要把一级缓存的数据清空,查询的时候没有通过数据库来就可以证明了
方案一.使用增删改就可以清空一级缓存 ,但是不能确定是否对二级缓存也有影响
方案二.一级缓存的生命周期和SqlSession是一致的所以在一次查询完后关闭SqlSession
- 增删改对二级缓存的影响
本例说明以下几点内容: 1.说明增删改同样也会清空二级缓存 2.对于二级缓存的清空实质上是对所查找key对应的value置为null,而并非将<key,value>对,即Entry对象删除 3.从DB中进行select查询的条件是: 1)缓存中根本就不存在这个key 2)缓存中存在该key所对应的Entry对象,但其value为null
4.命中率是在缓存中根据key来查的,因为key存在所以命中率为0.5,但是所对应的value为null,所以需要再次去数据库中查找,也就有了第二次sql
5.如果不想让增删改对二级缓存产生影响 就在<insert flushCache="false"即可,但是对于一级缓存这个是没用的,无论如何都会清空
- 二级缓存的配置
eviction:逐出策略。当二级缓存中的对象达到最大值时,就需要通过逐出策略将缓存中的对象移出缓存。默认为LRU。还有FIFO。
flushInterval:刷新缓存的时间间隔,单位毫秒。这里的刷新即清空缓存。一般不指定,即当执行增删改时刷新缓存。
readOnly:设置缓存中数据是否只读。只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。但读写的缓存会返回缓存对象的拷贝。这会慢一些,但是安全,因此默认是false。
size:二级缓存中可以存放的最多对象个数。默认为1024个。
- 二级缓存的关闭
在主配置文件中的<settings>标签下加入
<settings> <setting name="cacheEnabled" value="false"/> </settings>
二级缓存的局部关闭 在select下加入useCache="false"
<select id="selectStudentById" useCache="false" resultType="Student"> select id,name,age,score from student2 where id = #{id} </select>
- 二级缓存的使用原则
- 多个namespace不要操作同一张表
由于二级缓存中的数据是基于namespace的,即不同namespace中的数据互不干扰。若某个用户在某个namespace下对表执行了增删改操作,该操作只会引发当前namespace下的二级缓存的刷新,而对其他namespace下的二级缓存没有影响。这样的话,其他二级缓存中的数据依然是为更新的数据,也就出现了多个namespace中的数据不一样的现象。
-
- 不要在关联关系表上执行增删改操作
一个namespace一般是对同一个表进行操作,若表间存在关联关系,也就意味着同一个表可能就会出现在多个namespace中。这样就存在一个风险,若某一个namespace中对一个表进行增删改操作时影响到了其关联表的数据,而这个关联表的数据的修改只会刷新当前namespace下的二级缓存,而对另一namespace下的二级缓存数据没有影响。这也是多个namespace下对同一张表的操作。
-
- 查询多于修改时使用二级缓存
在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。
3.ehcache二级缓存
mybatis的特长是SQL操作,缓存数据管理不是其特长,为了提高缓存的性能,mybatis允许使用第三方缓存产品,ehCache就是其中一种。
注意:使用ehcache二级缓存,实体类无需实现序列化接口。
- ehcache二级缓存的启动
- 导入包
-
- 映射文件cache 类型改成ehcache
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
-
- 加入ehcache.xml文件
在ehcache-core-2.6.8.jar 包下有一个 ehcache-failsafe.xml 将它复制出来改成ehcache.xml 同时放在src目录下
若是直接将实体类的序列化接口去掉会报错 使用缓存时出现java.io.NotSerializableException:xxx.xxx.xxx.Bean解决办法 因为ehcache想将实体类写入磁盘但是这必须通过实体类的序列化才可以 解决方法: 1.开发过程中如果想缓存某个JavaBean,请确保它所引用的对象都implents Serializable,如果某个对象不需要被cache,可以加上transient关键字,否则Ehcache每次都通过引用查找的方法去保存所有实例数据到磁盘.最终会失败. 2.ehcache.xml中 <defaultCache overflowToDisk="false" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxElementsOnDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache>
ehcache在不同mapper中的个性化设置 就是在cache里的property标签下 加name 和 value的值