第一章:Mybatis 的缓存机制介绍
- Mybatis 包含了一个非常强大的查询缓存特性,它可以非常方便的配置和定制。缓存可以极大的提升查询效率。
- Mybatis 系统中默认定义了两级缓存:一级缓存和二级缓存。
- 默认情况下,只有一级缓存(SqlSession 级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,它是基于 namespace 级别的缓存。
- 为了提高扩展性。Mybatis 定义了缓存接口 Cache 。我们可以通过实现 Cache 接口来自定义二级缓存。
第二章:准备工作
- 导入相关 jar 包的 Maven 坐标:
<dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.1</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.21</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13</version><scope>test</scope></dependency>
- sql 脚本
DROP TABLE IF EXISTS `employee`;CREATE TABLE `employee` (`id` int(11) NOT NULL AUTO_INCREMENT,`last_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,`gender` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
- db.properties
jdbc.url=jdbc:mysql://192.168.134.100:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&allowMultiQueries=truejdbc.driverClass=com.mysql.cj.jdbc.Driverjdbc.username=rootjdbc.password=123456
- log4j.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"><log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"><appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"><param name="Encoding" value="UTF-8" /><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /></layout></appender><logger name="java.sql"><level value="debug" /></logger><logger name="org.apache.ibatis"><level value="info" /></logger><root><level value="debug" /><appender-ref ref="STDOUT" /></root></log4j:configuration>
- Employee.java
package com.sunxiaping.domain;import org.apache.ibatis.type.Alias;import java.io.Serializable;@Alias("emp")public class Employee implements Serializable {private Integer id;private String lastName;private String email;private String gender;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getLastName() {return lastName;}public void setLastName(String lastName) {this.lastName = lastName;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}@Overridepublic String toString() {return "Employee{" +"id=" + id +", lastName='" + lastName + '\'' +", email='" + email + '\'' +", gender='" + gender + '\'' +'}';}}
- EmployeeMapper.java
package com.sunxiaping.mapper;public interface EmployeeMapper {}
- EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.sunxiaping.mapper.EmployeeMapper"></mapper>
- mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties resource="db.properties"></properties><settings><!-- 开启自动驼峰命名规则映射 --><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 开启对jdbcType的NULL的支持 --><setting name="jdbcTypeForNull" value="NULL"/><!-- 开启延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><!-- 开启按需加载 --><setting name="aggressiveLazyLoading" value="false"/></settings><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driverClass}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><package name="com.sunxiaping.mapper"/></mappers></configuration
第三章:一级缓存
3.1 一级缓存体验
和数据库同一次会话期间查询到的数据会放在本地缓存中,如果以后需要获取相同的数据,直接从缓存中取,而不需要再去查询数据库。
示例:
- EmployeeMapper.java
package com.sunxiaping.mapper;import com.sunxiaping.domain.Employee;public interface EmployeeMapper {Employee findById(Integer id);}
- EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.sunxiaping.mapper.EmployeeMapper"><!--抽取可重用的SQL片段,方便后面引用--><sql id="emp_sql" >id as id,last_name as lastName,email as email ,gender as gender</sql><select id="findById" resultType="com.sunxiaping.domain.Employee">SELECT <include refid="emp_sql"/>FROM employeeWHERE id = #{id,jdbcType=INTEGER}</select></mapper>
- 测试:
package com.sunxiaping;import com.sunxiaping.domain.Employee;import com.sunxiaping.mapper.EmployeeMapper;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.After;import org.junit.Before;import org.junit.Test;import java.io.IOException;import java.io.InputStream;public class EmployeeTest {SqlSessionFactory sqlSessionFactory = null;SqlSession sqlSession = null;EmployeeMapper employeeMapper = null;@Beforepublic void before() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);sqlSession = sqlSessionFactory.openSession(true);employeeMapper = sqlSession.getMapper(EmployeeMapper.class);}@Afterpublic void after() {if (null != sqlSession) {sqlSession.close();}}@Testpublic void testFindById() {Employee employee1 = employeeMapper.findById(1);Employee employee2 = employeeMapper.findById(1);System.out.println(employee1 == employee2);}}
- 日志:

3.2 一级缓存失效的四种情况
- ① SqlSession不同,一级缓存不同。
- ② SqlSession相同,但是查询条件不同(当前一级缓存中还没有这个数据),一级缓存不同。
- ③ SqlSession相同,两次查询期间执行了增删改操作(这次增删改可能对当前数据有影响),一级缓存不同。
- ④ SqlSession相同,手动清空了缓存,一级缓存不同。
第四章:二级缓存
4.1 二级缓存的介绍
- 基于 namespace 级别的缓存,一个 namespace 对应一个二级缓存。
- 工作机制:
- ① 一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中;
- ② 如果会话关闭,一级缓存中的数据会被保存到二级缓存中。
- ③ 新的会话查询信息,就可以参照二级缓存。
- ④ 对于一个 SqlSession 来说,如果通过 EmployeeMapper 查询到 Employee 对象,和通过 DepartmentMapper 查询到 Department 对象,放到二级缓存中的位置是不同的。
4.2 二级缓存的使用
步骤:
① 全局配置文件中开启二级缓存。
<settings><!-- 开启自动驼峰命名规则映射 --><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 开启对jdbcType的NULL的支持 --><setting name="jdbcTypeForNull" value="NULL"/><!-- 开启延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><!-- 开启按需加载 --><setting name="aggressiveLazyLoading" value="false"/><!-- 开启二级缓存 --><setting name="cacheEnabled" value="true"/></settings>
② 需要在使用二级缓存的映射文件处使用 cache 配置缓存。
<!--cache标签:配置缓存的相关属性- evication:缓存回收策略- LRU:最近最少使用的,移除最长时间不被使用的对象,默认值。- FIFO:先进先出,按对象进入缓存的顺序来移出它们。- SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象。- WEAK:弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象。- flushInterval:刷新时间,单位是毫秒。- 缓存多长时间清空一次,默认不清空。- size:缓存存放多少元素,正整数。- readOnly:是否只读。- true:只读缓存。- Mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。- Mybatis为了加快获取速度,直接将数据在缓存中的引用交给用户。- 不安全,速度快。- false:读写缓存。- Mybatis认为获取的数据可能会被修改。- Mybatis会利用序列化&&反序列化的技术克隆一份新的数据给你。- 安全,速度慢。- type:指定自定义缓存的全类名。- 实现Cache接口即可--><cache eviction="LRU" flushInterval="60000" readOnly="false" size="1024" />
③ 注意 POJO 需要实现 java.io.Serializable 接口。
示例:
- mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><properties resource="db.properties"></properties><settings><!-- 开启自动驼峰命名规则映射 --><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 开启对jdbcType的NULL的支持 --><setting name="jdbcTypeForNull" value="NULL"/><!-- 开启延迟加载 --><setting name="lazyLoadingEnabled" value="true"/><!-- 开启按需加载 --><setting name="aggressiveLazyLoading" value="false"/><!-- 开启二级缓存 --><setting name="cacheEnabled" value="true"/></settings><environments default="development"><environment id="development"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="${jdbc.driverClass}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><databaseIdProvider type="DB_VENDOR"><!--为不同的数据库厂商起别名--><property name="MySQL" value="mysql"/><property name="Oracle" value="oracle"/><property name="SQL Server" value="sqlserver"/></databaseIdProvider><mappers><package name="com.sunxiaping.mapper"/></mappers></configuration>
- EmployeeMapper.java
package com.sunxiaping.mapper;import com.sunxiaping.domain.Employee;public interface EmployeeMapper {Employee findById(Integer id);}
- EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.sunxiaping.mapper.EmployeeMapper"><!--cache标签:配置缓存的相关属性- evication:缓存回收策略- LRU:最近最少使用的,移除最长时间不被使用的对象,默认值。- FIFO:先进先出,按对象进入缓存的顺序来移出它们- SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象- WEAK:弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象- flushInterval:刷新时间,单位是毫秒。- 缓存多长时间清空一次,默认不清空。- size:缓存存放多少元素,正整数。- readOnly:是否只读- true:只读缓存。- Mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。- Mybatis为了加快获取速度,直接将数据在缓存中的引用交给用户。- 不安全,速度快。- false:读写缓存。- Mybatis认为获取的数据可能会被修改。- Mybatis会利用序列化&&反序列化的技术克隆一份新的数据给你。- 安全,速度慢。- type:指定自定义缓存的全类名。- 实现Cache接口即可--><cache eviction="LRU" flushInterval="60000" readOnly="false" size="1024"/><sql id="emp_sql">id as id,last_name as lastName,email as email ,gender as gender</sql><select id="findById" resultType="com.sunxiaping.domain.Employee">SELECT<include refid="emp_sql"/>FROM employeeWHERE id = #{id,jdbcType=INTEGER}</select></mapper>
- 测试:
package com.sunxiaping;import com.sunxiaping.domain.Employee;import com.sunxiaping.mapper.EmployeeMapper;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import org.junit.Before;import org.junit.Test;import java.io.IOException;import java.io.InputStream;public class EmployeeTest {SqlSessionFactory sqlSessionFactory = null;@Beforepublic void before() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);}@Testpublic void testSecondLevelCache() {SqlSession sqlSession = sqlSessionFactory.openSession(true);SqlSession sqlSession2 = sqlSessionFactory.openSession(true);EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);EmployeeMapper employeeMapper2 = sqlSession2.getMapper(EmployeeMapper.class);Employee employee1 = employeeMapper.findById(1);System.out.println("employee1 = " + employee1);sqlSession.close();//第二次查询是从二级缓存中拿到的数据,并没有发送新的SQLEmployee employee2 = employeeMapper2.findById(1);System.out.println("employee2 = " + employee2);sqlSession2.close();}}
4.3 缓存有关的设置和属性
- 全局配置文件中的 cacheEache 如果设置为 false,将会关闭二级缓存;但是一级缓存一直打开。
- Mapper 映射文件的每个 select 标签的都有 useCache 属性,如果设置为 false ,将会不使用二级缓存;但是一级缓存一直打开。
- Mapper 映射文件中的 insert、delete、update 标签的 flushCache 属性,默认情况下是 true ,意味着执行之后将会清空一级缓存和二级缓存;而 select 标签的 flushcache 属性,默认情况下是 false ,不会清空一级缓存和二级缓存。
- 调用 sqlSessio n的 clearCache() 方法只会清空一级缓存。
- 全局配置文件中的 localCacheScope 默认值是 SESSION ,当前会话的所有数据保存在会话中。如果设置为 STATEMENT ,将禁用一级缓存。
- 当在某一个作用域(一级缓存或二级缓存)进行了增删改操作后,默认情况下该作用域的所有 select 中的缓存将被清空。
4.4 缓存原理图

4.5 第三方缓存整合 Mybatis
4.5.1 Ehcache 概述
- Ehcache 是一个纯 Java 的进程内缓存框架,具有快速、精干等特点,是 Hibernate 中默认的 CacheProvide r。
- Mybatis 定义了 Cache 接口方便我们进行自定义扩展,而 mybatis-ehcache 就对Ehcache进行了整合。
4.5.2 Ehcache 整合 Mybatis 步骤
- 导入 ehcache 包、mybatis-ehcache 的整合包以及日志包的 Maven 坐标:
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.0.3</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.6.1</version></dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId><version>1.6.1</version><scope>test</scope></dependency>
- 编写 ehcache.xml
<?xml version="1.0" encoding="UTF-8"?><ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"><!-- 磁盘保存路径 --><diskStore path="D:\ehcache" /><defaultCachemaxElementsInMemory="10000"maxElementsOnDisk="10000000"eternal="false"overflowToDisk="true"timeToIdleSeconds="120"timeToLiveSeconds="120"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"></defaultCache></ehcache><!--属性说明:diskStore:指定数据在磁盘中的存储位置。defaultCache:当借助CacheManager.add("demoCache")创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略以下属性是必须的:maxElementsInMemory - 在内存中缓存的element的最大数目maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断overflowToDisk - 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上以下属性是可选的:timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.diskPersistent - 在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候, 移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)-->
- 在 Mapper 的映射文件中配置 cache 标签
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.sunxiaping.mapper.EmployeeMapper"><!--配置Ehcache缓存--><cache type="org.mybatis.caches.ehcache.EhcacheCache"/><sql id="emp_sql">id as id,last_name as lastName,email as email ,gender as gender</sql><select id="findById" resultType="com.sunxiaping.domain.Employee">SELECT<include refid="emp_sql"/>FROM employeeWHERE id = #{id,jdbcType=INTEGER}</select></mapper>
4.5.3 参照缓存
如果想在命名空间中共享相同的缓存配置和示例,可以使用 cache-ref 标签来引用另外一个缓存。
示例:
- DepartmentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.sunxiaping.mapper.DepartmentMapper"><!-- 参照缓存 --><cache-ref namespace="com.sunxiaping.mapper.EmployeeMapper"/></mapper>
4.6 Tomcat 、二级缓存 、HttpSession 、HttpServletReqeust 和 SqlSession(事务)之间的关系

