第5章 MyBatis缓存机制 视频讲解 学习目标 理解MyBatis一级缓存机制,能够描述一级缓存机制的概念和特点。 理解MyBatis二级缓存机制,能够描述二级缓存机制的概念和特点。 掌握MyBatis处理一级缓存的方法,能够灵活运用一级缓存机制。 掌握MyBatis处理二级缓存的方法,能够灵活运用二级缓存机制。 掌握MyBatis集成EhCache缓存的方法,能够准确配置EhCache缓存。 为了降低高并发访问给数据库带来的压力,大型企业项目中都会使用缓存。使用缓存可以降低磁盘I/O操作、减少程序与数据库的交互,帮助程序迅速获取所需数据,提升系统响应速度,对优化系统整体性能具有重要意义。本章将对MyBatis的缓存种类和EhCache缓存进行讲解。 5.1MyBatis缓存分类 在实际业务场景中,如果经常执行相同SQL语句的查询操作,会导致频繁与数据库交互,从而造成磁盘性能下降和资源浪费的情况。MyBatis的缓存机制很好地解决了这一类问题,MyBatis缓存将SQL语句或结果保存在内存中,以便下次执行相同的SQL时直接从缓存中读取,从而不用再次访问数据库。本节将对MyBatis的一级缓存和二级缓存的概念、特点及应用进行讲解。 5.1.1一级缓存 1. MyBatis一级缓存的概念及特点 MyBatis一级缓存机制是指在同一个SqlSession中,对相同的SQL语句和参数,只执行一次数据库查询,第一次查询的结果会被缓存起来,后续的查询会直接从缓存中获取,从而提高查询效率。 MyBatis一级缓存机制默认开启,它使用一个HashMap对象来存储缓存数据,key为hashCode+sqlId+SQL语句,value为查询结果对象。 MyBatis一级缓存机制有以下特点。 一级缓存是SqlSession级别的,不同的SqlSession之间的缓存是相互隔离的。 一级缓存只对select语句有效,对insert、update、delete语句无效。 MyBatis一级缓存也会有缓存失效或清空的情况,具体场景如下。 当执行任何insert、update、delete语句时,会清空当前SqlSession的所有缓存。 当执行不同的SQL语句或参数时,会重新生成新的key值,从而导致缓存失效。 当手动调用sqlSession.clearCache()方法时,会清空当前SqlSession的所有缓存。 当SqlSession关闭或提交时,会清空当前SqlSession的所有缓存。 需要注意的是,sqlSession是SqlSession类的对象。 2. MyBatis一级缓存的应用 在深入理解MyBatis一级缓存的概念、特点和应用场景后,这里通过一个示例演示MyBatis一级缓存的应用,从而掌握MyBatis一级缓存的机制和使用,具体步骤如下。 (1) 使用第2章2.5节中的数据库textbook和数据表education。 (2) 在IDEA中新建chapter05项目,将MyBatis的JAR包和MySQL驱动的JAR包添加到WEBINF下的lib文件夹中。 (3) 创建Education类,该类和例21所示内容相同,此处不再赘述。 (4) 创建mybatisconfig.xml配置文件,具体代码可参照例22,此处不再赘述。 (5) 创建映射文件EducationMapper.xml,具体代码如例51所示。 例51EducationMapper.xml。 1<?xml version="1.0" encoding="UTF-8"?> 2<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 3"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> 4<mapper namespace="education"> 5<select id="findEducationById" 6resultType="com.qfedu.pojo.Education"> 7select * from education where id = #{id} 8</select> 9<update id="updateEducation" 10parameterType="com.qfedu.pojo.Education"> 11update education set price = #{price} where id =#{id} 12</update> 13</mapper> 在例51中,第7行代码表示根据id查询教材的SQL语句,第11行代码表示根据id修改教材的SQL语句。 (6) 创建TestCache01类用于测试MyBatis一级缓存机制,具体代码如例52所示。 例52TestCache01.java。 1public class TestCache01 { 2public static void main(String[] args) { 3/*创建输入流*/ 4InputStream inputStream = null; 5/*将MyBatis配置文件转换为输入流*/ 6try { 7inputStream = 8Resources.getResourceAsStream("mybatis-config.xml"); 9} catch (IOException e) { 10e.printStackTrace(); 11} 12/*通过SqlSessionFactoryBuilder创建SqlSessionFactory对象*/ 13SqlSessionFactory build = 14new SqlSessionFactoryBuilder().build(inputStream); 15/*通过SqlSessionFactory创建SqlSession对象*/ 16SqlSession sqlSession = build.openSession(true); 17Education education1 = sqlSession.selectOne( 18"education.findEducationById", 1); 19System.out.println(education1.toString()); 20System.out.println("-----------------------------"); 21Education education2 = sqlSession.selectOne( 22"education.findEducationById", 1); 23System.out.println(education2.toString()); 24/*关闭事务*/ 25sqlSession.close(); 26} 27} 执行TestCache01类,MyBatis一级缓存测试结果如图51所示。 图51MyBatis一级缓存测试结果 从图51的控制台打印结果可以看出,执行两次相同的查询语句,这是因为使用了MyBatis一级缓存机制。当程序第二次执行相同的查询语句时,日志并没有发出SQL语句,而是直接从一级缓存中获取了数据。 (7) 新建TestCache02类,该类用于测试因修改数据信息导致一级缓存失效的场景,具体代码如例53所示。 例53TestCache02.java。 1public class TestCache02 { 2public static void main(String[] args) { 3/*创建输入流*/ 4InputStream inputStream = null; 5/*将MyBatis配置文件转换为输入流*/ 6try { 7inputStream = 8Resources.getResourceAsStream("mybatis-config.xml"); 9} catch (IOException e) { 10e.printStackTrace(); 11} 12/*通过SqlSessionFactoryBuilder创建SqlSessionFactory对象*/ 13SqlSessionFactory build = 14new SqlSessionFactoryBuilder().build(inputStream); 15/*通过SqlSessionFactory创建SqlSession对象*/ 16SqlSession sqlSession = build.openSession(true); 17//查询Id为1的数据信息 18Education education1 = 19sqlSession.selectOne("education.findEducationById", 1); 20System.out.println(education1.toString()); 21//修改Id为2的价格信息 22Education education2 = new Education(); 23education2.setId(2); 24education2.setPrice(19); 25sqlSession.update("education.updateEducation",education2); 26//再次查询Id为1的数据信息 27Education education3 = 28sqlSession.selectOne("education.findEducationById", 1); 29System.out.println(education3.toString()); 30/*关闭事务*/ 31sqlSession.close(); 32} 33} 执行TestCache02类,MyBatis一级缓存失效的测试结果如图52所示。 图52MyBatis一级缓存失效的测试结果 从图52的控制台打印结果可以看出,程序首先查询Id为1的教材信息,其次更新Id为2的教材信息,然后再次查询Id为1的教材信息。由于程序执行了更新操作,符合本节介绍的MyBaits一级缓存失效的情况之一,所以当再次查询Id为1的教材信息时,缓存失效,只能再次执行SQL语句从数据库中读取结果。 5.1.2二级缓存 1. MyBatis二级缓存的概念及特点 MyBatis二级缓存机制是指在不同的SqlSession中,对相同的SQL语句和参数,只执行一次数据库查询,第一次查询的结果会被缓存起来,后续的查询会直接从缓存中获取,从而提高查询效率。 MyBatis二级缓存机制默认是关闭的,需要手动开启和配置。它使用一个Cache对象来存储缓存数据,同时支持自定义缓存的实现类或使用第三方缓存方案。 MyBatis二级缓存机制有以下特点。 二级缓存是Mapper级别的缓存,多个SqlSession可以共享同一个Mapper的缓存,也称为全局缓存。 二级缓存只对select语句有效,对insert、update、delete语句无效。 MyBatis二级缓存也会有失效或清空的情况,具体场景如下。 当执行任何insert、update、delete语句时,会刷新当前Mapper的所有缓存。 当执行不同的SQL语句或参数时,会重新生成新的key值,从而导致缓存失效。 当手动调用sqlSession.clearCache()方法时,会清空当前SqlSession的所有缓存。 当SqlSession关闭或提交时,会清空当前SqlSession的所有缓存。 需要注意的是,sqlSession是SqlSession类的对象。 2. MyBatis二级缓存的应用 在深入理解MyBatis二级缓存的概念、特点和应用场景后,这里通过一个示例演示MyBatis二级缓存的应用,从而掌握MyBatis二级缓存的机制和使用,具体步骤如下。 (1) 使用本章5.1.1节中的chapter05项目。 (2) 在映射文件EducationMapper.xml中的<mapper>元素下添加如下代码。 <cache></cache> 上述代码表示开启二级缓存机制。 (3) Education类需要实现序列化接口,具体代码如下。 public class Education implements Serializable {} (4) 新建TestCache03类用于测试二级缓存机制,具体代码如例54所示。 例54TestCache03.java。 1public class TestCache03 { 2public static void main(String[] args) { 3/*创建输入流*/ 4InputStream inputStream = null; 5/*将MyBatis配置文件转化为输入流*/ 6try { 7inputStream = 8Resources.getResourceAsStream("mybatis-config.xml"); 9} catch (IOException e) { 10e.printStackTrace(); 11} 12/*通过SqlSessionFactoryBuilder创建SqlSessionFactory对象*/ 13SqlSessionFactory build = 14new SqlSessionFactoryBuilder().build(inputStream); 15/*通过SqlSessionFactory创建SqlSession对象*/ 16SqlSession sqlSession1 = build.openSession(true); 17SqlSession sqlSession2 = build.openSession(true); 18Education education1 = 19sqlSession1.selectOne("education.findEducationById", 1); 20//事务提交 21sqlSession1.commit(); 22System.out.println(education1.toString()); 23System.out.println("-----------------------------"); 24Education education2 = 25sqlSession2.selectOne("education.findEducationById", 1); 26sqlSession2.commit(); 27System.out.println(education2.toString()); 28/*关闭事务*/ 29sqlSession1.close(); 30sqlSession2.close(); 31} 32} 执行TestCache03类,MyBatis二级缓存的测试结果如图53所示。 图53MyBatis二级缓存的测试结果 从图53的控制台打印结果可以看出,在同一个Java程序中使用两个不同的SqlSession对象执行相同的查询语句,由于在配置文件中启用了MyBatis二级缓存,当程序第二次执行该查询语句时,日志并没有发出SQL语句,而是直接从二级缓存中读取了数据。 (5) 新建TestCache04类,该类用于测试因修改数据信息导致二级缓存失效的场景,具体代码如例55所示。 例55TestCache04.java。 1public class TestCache04 { 2public static void main(String[] args) { 3/*创建输入流*/ 4InputStream inputStream = null; 5/*将MyBatis配置文件转换为输入流*/ 6try { 7inputStream = 8Resources.getResourceAsStream("mybatis-config.xml"); 9} catch (IOException e) { 10e.printStackTrace(); 11} 12/*通过SqlSessionFactoryBuilder创建SqlSessionFactory对象*/ 13SqlSessionFactory build = 14new SqlSessionFactoryBuilder().build(inputStream); 15/*通过SqlSessionFactory创建SqlSession对象*/ 16SqlSession sqlSession1 = build.openSession(true); 17SqlSession sqlSession2 = build.openSession(true); 18SqlSession sqlSession3 = build.openSession(true); 19//查询Id为1的数据信息 20Education education1 = 21sqlSession1.selectOne("education.findEducationById", 1); 22sqlSession1.commit(); 23System.out.println(education1.toString()); 24//修改Id为2的价格信息 25Education education2 = new Education(); 26education2.setId(2); 27education2.setPrice(18); 28sqlSession2.update("education.updateEducation",education2); 29sqlSession2.commit(); 30//再次查询Id为1的数据信息 31Education education3 = 32sqlSession3.selectOne("education.findEducationById", 1); 33sqlSession3.commit(); 34System.out.println(education3.toString()); 35/*关闭事务*/ 36sqlSession1.close(); 37sqlSession2.close(); 38sqlSession3.close(); 39} 40} 执行TestCache04类,MyBatis二级缓存失效的测试结果如图54所示。 图54MyBatis二级缓存失效的测试结果 从图54的控制台打印结果可以看出,程序首先查询id为1的教材信息,其次更新id为2的教材信息,然后再次查询id为1的教材信息。由于程序执行了更新操作,符合本节介绍的MyBaits二级缓存失效的情况之一,所以当再次查询id为1的教材信息时,缓存失效,只能再次执行SQL语句从数据库中读取结果。 5.2EhCache缓存 在实际开发中,很多项目会用到分布式架构。分布式架构是将项目拆分成若干个子项目并使它们协同发挥功能的解决方案。在分布式系统架构下,为了提升系统性能,通常会采用分布式缓存对缓存数据进行集中管理。由于MyBatis自身无法实现分布式缓存,需要整合其他分布式缓存框架,如EhCache缓存。本节将对EhCache缓存概念、EhCache下载和MyBatis整合EhCache缓存进行讲解。 5.2.1EhCache缓存简介 EhCache缓存是一个纯Java进程的缓存框架,具有快速、精干等特点。EhCache主要面向通用缓存、Java EE和轻量级容器。 ehcache.xml是EhCache的配置文件,一般存放在系统应用的classpath中。EhCache的配置文件有其自身特有的层次结构,具体结构如下所示。 1<?xml version="1.0" encoding="UTF-8"?> 2<ehcache> 3<diskStore path=""/> 4<defaultCache 5maxElementsInMemory="" 6maxElementsOnDisk="" 7eternal="" 8overflowToDisk="" 9diskPersistent="" 10timeToIdleSeconds="" 11timeToLiveSeconds="" 12diskSpoolBufferSizeMB="" 13diskExpiryThreadIntervalSeconds="" 14memoryStoreEvictionPolicy="" 15/> 16<cache name="" eternal="" 17maxElementsInMemory="" 18overflowToDisk="" 19diskPersistent="" 20timeToIdleSeconds="" 21timeToLiveSeconds="" 22memoryStoreEvictionPolicy="" /> 23</ehcache> 上述配置信息中,第3行代码的<diskStore>元素用于指定一个文件目录。当EhCache把数据写到硬盘上时,会把数据写到该文件目录下。第16行的<cache>元素可以设置自定义的配置信息。第4行代码中的<defaultCache>元素包含了一些属性用于定义配置信息,具体如表51所示。 表51<defaultCache>元素的属性 属 性 名 称说明 maxElementsInMemory指定内存中最大缓存对象数 maxElementsOnDisk指定磁盘中最大缓存对象数 eternal指定缓存的elements是否永远不过期 overflowToDisk指定当内存缓存溢出的时候是否将过期的element缓存到磁盘上 timeToIdleSeconds指定EhCache中的数据前后两次被访问的时间间隔 timeToLiveSeconds指定缓存element的有效生命期 diskPersistent在VM重启时是否启用磁盘保存EhCache中的数据 表51列举出了EhCache配置文件中<defaultCache>元素的属性,开发人员可根据需要调整EhCache缓存的具体功能。 5.2.2EhCache的下载 (1) 访问EhCache官网或者EhCache的GitHub网址,根据实际场景选择对应版本号进行下载。EhCache官网下载页面如图55所示。 图55EhCache官网下载页面 (2) 首先将下载好的压缩包文件解压,然后打开解压后的文件夹,找到mybatisehcache1.0.3.jar文件,最后打开lib目录,找到ehcachecore2.6.8.jar文件。这两个文件即为MyBatis整合EhCache缓存所需的JAR包。EhCache的JAR包目录如图56所示。 图56EhCache的JAR包目录 5.2.3MyBatis整合EhCache缓存 MyBatis整合EhCache缓存的具体步骤如下所示。 1. 引入EhCache的JAR包 将mybatisehcache1.0.3.jar文件和lib目录下的ehcachecore2.6.8.jar文件复制到新项目chapter05的lib目录下,完成JAR包的导入。 2. 配置EhCache的type属性 在Education.xml映射文件中,配置<cache>标签的type属性,具体代码如下所示。 <cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache> 3. 配置EhCache 在chapter05项目的resource目录下新建EhCache的配置文件ehcache.xml,具体代码如例56所示。 例56ehcache.xml。 1<?xml version="1.0" encoding="UTF-8"?> 2<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> 4<diskStore path="D:/ehcache/"/> 5<defaultCache 6maxElementsInMemory="10" 7maxElementsOnDisk="100000000" 8eternal="false" 9overflowToDisk="true" 10diskPersistent="false" 11timeToIdleSeconds="120" 12timeToLiveSeconds="120" 13diskExpiryThreadIntervalSeconds="120" 14memoryStoreEvictionPolicy="LRU"/> 15</ehcache> 在例56中,<diskStore>的path属性设置缓存地址为D盘下的ehcache目录,maxElementsInMemory指定在内存中缓存元素的最大数目为10; 因为本例要演示缓存溢出后数据会存入磁盘的效果,所以此处的值需要设置得偏低,在实际开发中需要根据具体需求设置; overflowToDisk属性值为true,当内存缓存溢出时,过期的element会被缓存到磁盘上。 4. 执行结果 执行TestCache03类的main()方法,EhCache缓存执行结果如图57所示。 图57EhCache缓存执行结果 从图57的执行结果可以看出,EhCache缓存和使用MyBatis默认的二级缓存结果相同,当第2个SqlSession对象执行相同的查询SQL语句时,命中率(Cache Hit Ratio)为0.5,程序没有发出SQL语句,这就说明,程序从EhCache缓存中获取了数据。 打开D盘,可以发现D盘中出现了ehcache目录,打开D:\ehcache目录,该目录中存有EhCache缓存信息,如图58所示。 图58EhCache缓存信息 5.3本 章 小 结 本章首先介绍了MyBatis的一级缓存和二级缓存的特点及使用方法,然后对EhCache缓存概念、EhCache缓存的下载和MyBatis整合EhCache缓存进行了讲解。通过本章的学习,可以使读者更好地理解和应用MyBatis的缓存机制以及MyBatis与EhCache缓存的整合使用,从而提高应用的性能和稳定性。 5.4习题 一、 填空题 1. MyBatis的缓存分为和。 2. 一级缓存是级别的缓存,二级缓存是级别的缓存。 3. MyBatis缓存中,一级缓存默认是开启的,二级缓存默认是的。 4. 当数据库发生修改、删除或新增时,MyBatis缓存会。 5. 当执行两次相同的查询语句时,MyBatis会首先从中读取查询结果。 二、 选择题 1. 关于MyBatis一级缓存的描述,下列选项错误的是()。 A. 当程序对数据库执行了DML操作,MyBatis会删除一级缓存中的相关内容 B. 当一级缓存开启时,如果SqlSession第一次执行某查询语句,MyBatis会写入一级缓存 C. 当一级缓存开启时,如果SqlSession第二次执行某查询语句,MyBatis一定可以从一级缓存中读取数据 D. MyBatis一级缓存的作用域是SqlSession 2. 关于MyBatis的二级缓存,下列选项错误的是()。 A. MyBatis二级缓存的作用域是跨Mapper的 B. 在使用二级缓存时,MyBatis以namespace区分Mapper C. MyBatis二级缓存默认是开启的 D. 如果MyBatis二级缓存为可读写缓存,则被操作的POJO类需实现序列 3. 在<cache>元素的属性中,用于指定收回策略的是()。 A. EvictionB. flushInterval C. sizeD. readOnly 4. 关于MyBatis整合EhCache缓存,下列说法错误的是()。 A. MyBatis的二级缓存可以自定义缓存源 B. EhCache是一种获得广泛应用的开源分布式缓存框架 C. MyBatis自身可以实现分布式缓存 D. EhCache通常采用名称为ehcache.xml的配置文件实现功能配置 5. 在EhCache缓存的配置文件中,下列用于指定磁盘存储的是()。 A. <diskStore>B.<defaultCache> C.<cache>D.<ehcache> 三、 简答题 1. 请简述MyBatis一级缓存的概念和特点。 2. 请简述MyBatis二级缓存的概念和特点。 四、 操作题 通过MyBatis整合EhCache缓存的方法,查询表education中所有教材的信息,将查询结果存入EhCache缓存中。