12.查询缓存
Posted 白日梦想家12138
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了12.查询缓存相关的知识,希望对你有一定的参考价值。
查询缓存的使用,主要是为了提高查询访问速度。将用户对同一数据的重复查询过程简化,
不再每次均从数据库查询获取结果数据,从而提高访问速度。
MyBatis 的查询缓存机制,根据缓存区的作用域(生命周期)可划分为两种:一级查询 缓存与二级查询缓存。
一级查询缓存
MyBatis 一级查询缓存是基于 org.apache.ibatis.cache.impl.PerpetualCache 类的 HashMap 本地缓存,其作用域是 SqlSession。
在同一个 SqlSession 中两次执行相同的 sql 查询语句,第 一次执行完毕后,会将查询结果写入到缓存中,
第二次会从缓存中直接获取数据,而不再到 数据库中进行查询,从而提高查询效率。
当一个 SqlSession 结束后,该 SqlSession 中的一级查询缓存也就不存在了。
myBatis 默认 一级查询缓存是开启状态,且不能关闭。
一级缓存缓存的是相同 Sql 映射 id 的查询结果,而非相同 Sql 语句的查询结果。
因为 myBatis 内部对于查询缓存,无论是一级查询缓存还是二级查询缓存,其底层均使用一个 HashMap 实现:key 为 Sql 的 id 相关内容,value 为从数据库中查询出的结果。
1 public class MyTest { 2 private SqlSession sqlSession; 3 private StudentDAO studentDAO; 4 5 @Before 6 public void before() { 7 8 sqlSession = SqlSessionUtil.getSqlSession(); 9 studentDAO = sqlSession.getMapper(StudentDAO.class); 10 } 11 12 @After 13 public void after() { 14 if (sqlSession != null) { 15 sqlSession.close(); 16 } 17 } 18 19 // 测试一级缓存 即 SqlSession 缓存,是存在的 20 @Test 21 public void testSqlSessionCache() { 22 Student student = studentDAO.selectStudentById(1); 23 System.out.println(student); 24 25 // 只会发出一条SQL语句,说明第二次查询 是从 SqlSession 缓存中读取的 26 Student student2 = studentDAO.selectStudentById(1); 27 System.out.println(student2); 28 29 } 30 31 // 测试 一级缓存的 查询依据 32 // 缓存的底层实现是 一个 Map ,Map 的 value 是查询结果 33 // Map 的 key 即查询依据,使用 ORM架构(如MyBatis 和 Hibernate)不同,查询依据是不同的 34 // MyBatis 的 查询依据是 Sql 的 id(mapper.xml文件中定义的) + SQL 语句 + hash code 35 // Hibernate 的查询依据是:查询结果对象的id 36 @Test 37 public void testSqlSessionCache2() { 38 Student student = studentDAO.selectStudentById(1); 39 System.out.println(student); 40 41 // 在参数相同的 情况下,依然会发出 SQL语句,所以 缓存的查询依据 不像 hibernate那样 仅仅依赖于 对象的 id 42 Student student2 = studentDAO.selectStudentById2(1); 43 System.out.println(student2); 44 45 } 46 47 //测试 增删改 操作 对 一级缓存的 影响 48 @Test 49 public void testSqlSessionCache3() { 50 Student student = studentDAO.selectStudentById(1); 51 System.out.println(student); 52 53 studentDAO.delecteStudent(5); 54 55 //在执行 增删改 操作后,无论是否进行提交,会清空一级缓存 56 Student student2 = studentDAO.selectStudentById2(1); 57 System.out.println(student2); 58 59 } 60 }
内置二级查询缓存(默认是开启的)
myBatis 查询缓存的作用域是根据映射文件 mapper 的 namespace 划分的,相同namespace 的 mapper 查询数据存放在同一个缓存区域。不同 namespace 下的数据互不干扰。
无论是一级缓存还是二级缓存,都是按照 namespace 进行分别存放的。但一、二级缓存的不同之处在于,SqlSession 一旦关闭,则 SqlSession 中的数据将不存在,即一级缓存就不覆存在。而二级缓存的生命周期会与整个应用同步,与 SqlSession 是否 关闭无关。
使用二级缓存的目的,不是共享数据,因为 MyBatis 从缓存中读取数据的依据是 SQL 的 id,而非查询出的对象。所以,二级缓存中的数据不是为了在多个查询之间共享(所有查询 中只要查询结果中存在该对象的,就直接从缓存中读取,这是对数据的共享,Hibernate 中 的缓存就是为了共享,但 MyBaits 的不是),而是为了延长该查询结果的保存时间,提高系统性能。
myBatis 内置的二级缓存为 org.apache.ibatis.cache.impl.PerpetualCache。
二级缓存用法
二级缓存的存在性证明
增删改对二级缓存的影响
二级缓存的关闭
二级缓存的使用原则
1 public class MyTest { 2 private SqlSession sqlSession; 3 private StudentDAO studentDAO; 4 5 @After 6 public void after() { 7 if (sqlSession != null) { 8 sqlSession.close(); 9 } 10 } 11 12 // 测试默认的二级缓存 ,是存在的 13 // 二级缓存的开启 有两步:1.查询结果所涉及的实体类 需要实现 Serializable 序列化接口 14 // 2.在 mapper 映射文件中 添加 <cache/>标签 15 @Test 16 public void testSecoundLevelSession() { 17 sqlSession = SqlSessionUtil.getSqlSession(); 18 studentDAO = sqlSession.getMapper(StudentDAO.class); 19 Student student = studentDAO.selectStudentById(1); 20 System.out.println(student); 21 22 sqlSession.close(); 23 24 sqlSession = SqlSessionUtil.getSqlSession(); 25 studentDAO = sqlSession.getMapper(StudentDAO.class); 26 Student student2 = studentDAO.selectStudentById(1); 27 System.out.println(student2); 28 29 } 30 31 32 33 //测试 增删改 操作 对 二级缓存的 影响 34 //1.增删改 同样也会清空二级缓存 35 //2.二级缓存的存储同样用的是 Map,对于二级缓存的清空,实际上是将这个Map集合中 某个key 对应的value 置为 null, 36 // 而不是将Map里面一个一个的Entry清空,Key还在,所以还能找到 这个 key,但是因为没有相对应的value,所以缓存是无法命中的 37 //3.从DB中select (即发出SQL查询语句)的条件是 38 // 1.缓存中根本就不存在这个key 39 // 2.缓存中 存在 该key 对应的 Entry,但其value 为 null 40 @Test 41 public void testSecoundLevelSession2() { 42 sqlSession = SqlSessionUtil.getSqlSession(); 43 studentDAO = sqlSession.getMapper(StudentDAO.class); 44 Student student = studentDAO.selectStudentById(1); 45 System.out.println(student); 46 47 sqlSession.close(); 48 49 sqlSession = SqlSessionUtil.getSqlSession(); 50 studentDAO = sqlSession.getMapper(StudentDAO.class); 51 52 studentDAO.delecteStudent(2); 53 54 Student student2 = studentDAO.selectStudentById(1); 55 System.out.println(student2); 56 57 } 58 59 }
mapper.xml中 进行这个namespace下的二级缓存的配置
1 <!-- 二级缓存的使用原则 2 1.只能在一个命名空间下使用二级缓存 3 2.只能在单表上使用二级缓存(表之间不能有关联) //这点导致系统自带的 二级缓存 的实际用途不大 4 3.查询多于修改时,使用二级缓存 5 6 --> 7 8 <!-- cache有属性可以进行二级缓存的配置 eviction 逐出策略(采用什么算法来更新缓存)size(Map能够存放的元素的个数) readOnly flushInterval --> 9 <cache /> 10 11 <!-- 使用 flushCache 可以指明增删改操作不刷新二级缓存(对一级缓存没用) --> 12 <delete id="delecteStudent" flushCache="false" > 13 delete from student where id = #{id} <!-- 这里的id就是是直接传过来的一个整数,不是通过对象来进行传递的,所以#{id} 充当的是 占位符,{} 可以写任意字符 --> 14 </delete> 15 16 <update id="updateStudent"> 17 update student set name = #{name},age = #{age},score = #{score} where id = #{id} 18 </update> 19 20 <!-- 二级缓存的 局部关闭 (整个应用的二级缓存是开启的,但是当前 select 查询 不使用 二级缓存) --> 21 <select id="selectStudentById" useCache="false" resultType="com.mybatis.model.Student"> 22 select * from student where id = #{id} <!-- 这里 #{id} 同样是占位符--> 23 </select> 24 <select id="selectStudentById2" resultType="com.mybatis.model.Student"> 25 select * from student where id = #{id} <!-- 这里 #{id} 同样是占位符--> 26 </select>
MyBatis.xml
<settings> 2 <!-- 二级缓存默认是开启的 --> 3 <!-- 关闭二级缓存 默认是 true(全局性的关闭)(整个应用的二级缓存 全部关闭) --> 4 <setting name="cacheEnabled" value="false"/> </settings>
ehcache二级查询缓存
Mybatis的特长是SQL操作,缓存数据管理不是其特长,为了提高缓存的性能,MyBatis 允许使用第三方缓存产品
ehcache就是其中一种
1.导入Jar包
2.添加ehcache.xml
解压 EHCache 的核心 Jar 包 ehcache-core-2.6.8.jar,将其中的一个配置文件ehcache-failsafe.xml 直接放到项目的 src 目录下,并更名为 ehcache.xml
3.启动ehcache缓存机制
在映射文件的 mapper 中的<cache/>中通过 type 指定缓存机制为 Ehcache 缓存。默认为
myBatis 内置的二级缓存 org.apache.ibatis.cache.impl.PerpetualCache。
4.ehcache在不同mapper中的个性化设置
在 ehcache.xml 中设置的属性值,会对该项目中所有使用 ehcache 缓存机制的缓存区域 起作用。一个项目中可以有多个 mapper,不同的 mapper 有不同的缓存区域。对于不同缓存区域也可进行专门针对于当前区域的个性化设置,可通过指定不同 mapper 的<cache>属性 值来设置。
<cache>属性值的优先级高于 ehcache.xml 中的属性值
以上是关于12.查询缓存的主要内容,如果未能解决你的问题,请参考以下文章
Android获取各个应用程序的缓存文件代码小片段(使用AIDL)
Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题