一、MyBatis的缓存机制
MyBatis 在执行 DQL(select 语句)的时候,将查询结果放到缓存(内存)当中。如果下一次还是执行完全相同的 DQL 语句,直接从缓存中拿取数据,不再查询数据库,不在从硬盘上查找数据。这样,可以提高程序的执行效率。缓存机制的主要使用减少 IO(读文件和写文件) 的方式来提高效率。
MyBatis 的缓存包括:
- 一级缓存:将查询到的数据存储到 SqlSession 中
- 二级缓存:将查询到的数据存储到 SqlSessionFactory 中
- 或者集成其它第三方缓存:比如 EhCache【Java语言开发的】、Memchae【C语言开发的】
缓存只针对 DQL 语句,也就是说缓存机制只对应 select 语句
二、MyBatis的一级缓存
一级缓存是 SqlSession 级别的,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。一级缓存默认是开启的,不需要做任何配置。
一级缓存失效的四种情况:
- 不同的 SqlSession 对应不同的一级缓存
- 同一个 SqlSession 但是查询条件不同
- 同一个 SqlSession 两次查询期间执行了任何一次增删改操作,并且和表没有关系
- 同一个 SqlSession 两次查询期间手动清空了缓存,即执行了 clearCache()
①数据的准备
CREATE DATABASE IF NOT EXISTS db_test;
USE db_test;
CREATE TABLE IF NOT EXISTS t_emp(
emp_id INT PRIMARY KEY auto_increment,
emp_name VARCHAR(20),
age INT,
sex VARCHAR(10),
salary DOUBLE
);
INSERT INTO t_emp(emp_id,emp_name,age,sex,salary)
VALUES (null,'Sakura',10,"女",7300),
(null,'Mikoto',14,"女",7000),
(null,'Shana',15,'女',7100),
(null,'Kikyō',18,'女',6700),
(null,'Kagome',15,'女',6500);
②导入jar包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- junit 测试程序 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
③创建核心配置文件
创建 MyBatis 的核心配置文件,习惯上命名为 mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。配置 MyBatis 的核心配置文件中的标签必须按照固定的顺序:
properties --> settings --> typeAliases --> typeHandlers --> objectFactory --> objectWrapperFactory --> reflectorFactory --> plugins --> environments --> databaseIdProvider --> mappers
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 引入数据库连接信息的配置文件 -->
<properties resource="jdbc.properties"/>
<!-- settings标签的内容是MyBatis极为重要的调整设置,它们会改变MyBatis的运行时行为 -->
<settings>
<!-- 指定MyBatis所有日志的具体实现,未指定时将自动查找 -->
<setting name="logImpl" value="STDOUT_LOGGING"/>
<!-- 开启驼峰命名自动映射 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<typeAliases>
<!-- 将这个包下的所有的类全部起别名,别名就是类简名,不区分大小写 -->
<package name="star.light.pojo"/>
</typeAliases>
<!-- environments:配置多个连接数据库环境 -->
<environments default="development">
<!-- environment:配置某个具体的环境 -->
<environment id="development">
<!-- 设置事务管理方式 -->
<transactionManager type="JDBC"/>
<!-- 配置数据源 -->
<dataSource type="POOLED">
<!-- 设置连接数据库的驱动 -->
<property name="driver" value="${jdbc.driver}"/>
<!-- 设置连接数据库的地址 -->
<property name="url" value="${jdbc.url}"/>
<!-- 设置连接数据库的用户名 -->
<property name="username" value="${jdbc.username}"/>
<!-- 设置连接数据库的密码 -->
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<!-- 从Mapper接口所在的包的路径中加载 -->
<package name="star.light.mapper"/>
</mappers>
</configuration>
数据库连接信息的配置文件:jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_test
jdbc.username=root
jdbc.password=abc123
④创建MyBatis映射文件
MyBatis 的映射文件存放的目录要与 Mapper 接口的路径一致。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace用来指定命名空间的,防止id重复
如果在多个Mapper文件中,SQL语句的id重复,这时可以在Java程序中写namespace.id
实质上,本质上,MyBatis中的SQLId的完整写法为namespace.id
-->
<mapper namespace="star.light.mapper.EmpMapper">
<!-- Employee selectEmpById(Integer empId); -->
<select id="selectEmpByEmpId" resultType="Employee">
select emp_id, emp_name, age, sex, salary from t_emp where emp_id = #{empId}
</select>
<!-- int insertEmployee(Employee employee); -->
<insert id="insertEmployee">
insert into t_emp(emp_id,emp_name,age,sex,salary) values (#{empId},#{empName},#{age},#{sex},#{salary})
</insert>
</mapper>
⑤创建Mapper接口
package star.light.mapper;
import star.light.pojo.Employee;
public interface EmpMapper {
/**
* MyBatis 面向接口编程的两个一致:
* 1、映射文件的 namespace 要和 mapper 接口的全类名保持一致
* 2、映射文件中 SQL 语句的 id 要和 mapper 接口中方法名一致
*/
Employee selectEmpByEmpId(Integer empId);
int insertEmployee(Employee employee);
}
⑥创建实体类
package star.light.pojo;
public class Employee {
private Integer empId;
private String empName;
private Integer age;
private String sex;
private Double salary;
public Employee() {
}
public Employee(Integer empId, String empName, Integer age, String sex, Double salary) {
this.empId = empId;
this.empName = empName;
this.age = age;
this.sex = sex;
this.salary = salary;
}
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"empId=" + empId +
", empName='" + empName + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", salary=" + salary +
'}';
}
}
⑦封装SqlSessionUtil工具类
package star.light.util;
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 java.io.IOException;
public class SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory;
// 为了防止new对象,工具类的构造方法一般都是私有的
// 工具类中所有的方法都是静态的,直接采用类型即可调用,不需要new对象
private SqlSessionUtil(){}
// 静态代码块在类加载的时候执行
// SqlSesionUtil工具类在进行第一次加载的时候,解析MyBatis和核心配置文件,创建SqlSessionFactory对象
static {
try {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
/**
* 获取会话对象
* @return 会话对象
*/
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
⑧测试程序
package star.light.test;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import star.light.mapper.EmpMapper;
import star.light.pojo.Employee;
import star.light.util.SqlSessionUtil;
public class CacheTest {
@Test
public void testFirstLevelCache(){
SqlSession sqlSession1 = SqlSessionUtil.getSqlSession();
EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
Employee employee1 = mapper1.selectEmpByEmpId(1);
System.out.println(employee1);
EmpMapper mapper2 = sqlSession1.getMapper(EmpMapper.class);
Employee employee2 = mapper2.selectEmpByEmpId(1);
System.out.println(employee2);
// 获取不同的SqlSession对象,一级缓存失效
SqlSession sqlSession2 = SqlSessionUtil.getSqlSession();
EmpMapper mapper3 = sqlSession2.getMapper(EmpMapper.class);
Employee employee3 = mapper3.selectEmpByEmpId(1);
System.out.println(employee3);
// 手动清空一级缓存,会使一级缓存失效
sqlSession1.clearCache();
// 在这里执行了INSERT、DELETE、UPDATE中的任意一个语句,并且与要操作的表没有关系都会使一级缓存失效
int affectedRowCount = mapper3.insertEmployee(new Employee(null, "Akame", null, "女", 15000.0));
System.out.println("affectedRowCount = " + affectedRowCount);
Employee employee4 = mapper3.selectEmpByEmpId(1);
System.out.println(employee4);
// 程序执行到这里的时候,会将sqlsession1中的一级缓存写入到二级缓存中
sqlSession1.close();
// 程序执行到这里的时候,会将sqlsession2中的一级缓存写入到二级缓存中
sqlSession2.close();
}
}
三、MyBatis的二级缓存
二级缓存是 SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。
二级缓存开启的条件:
- 在核心配置文件中,设置全局配置属性 cacheEnabled="true",默认为 true,不需要设置
- 在映射文件中设置标签
<cache>
标签 - 查询的数据所转换的实体类类型必须实现序列化的接口,也就是必须实现 java.io.Serialzable 接口
- 二级缓存必须在 SqlSession 关闭或提交之后有效,一级缓存中的数据才会被写入到二级缓存当中,此时二级缓存才可用
二级缓存失效的情况:
- 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失
①在映射文件中设置标签 <cache>
标签
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
namespace用来指定命名空间的,防止id重复
如果在多个Mapper文件中,SQL语句的id重复,这时可以在Java程序中写namespace.id
实质上,本质上,MyBatis中的SQLId的完整写法为namespace.id
-->
<mapper namespace="star.light.mapper.EmpMapper">
<cache />
<!-- Employee selectEmpById(Integer empId); -->
<select id="selectEmpByEmpId" resultType="Employee">
select emp_id, emp_name, age, sex, salary from t_emp where emp_id = #{empId}
</select>
<!-- int insertEmployee(Employee employee); -->
<insert id="insertEmployee">
insert into t_emp(emp_id,emp_name,age,sex,salary) values (#{empId},#{empName},#{age},#{sex},#{salary})
</insert>
</mapper>
②实体类实现序列化接口
package star.light.pojo;
import java.io.Serializable;
public class Employee implements Serializable {
private Integer empId;
private String empName;
private Integer age;
private String sex;
private Double salary;
public Employee() {
}
public Employee(Integer empId, String empName, Integer age, String sex, Double salary) {
this.empId = empId;
this.empName = empName;
this.age = age;
this.sex = sex;
this.salary = salary;
}
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"empId=" + empId +
", empName='" + empName + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", salary=" + salary +
'}';
}
}
③在测试程序中添加如下测试方法
@Test
public void testSecondLevelCache() throws IOException {
// 这里只有同一个SqlSessioFactory对象,二级缓存对应的SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
// 这行代码结束之后,实际上数据缓存到一级缓存中(sqlSession1是一级缓存)
Employee employee1 = mapper1.selectEmpByEmpId(1);
System.out.println(employee1);
// 如果这里不关闭sqlSession1对象的话,二级缓存中还是没有数据
sqlSession1.close();
// 这行代码结束之后,实际上数据缓存到一级缓存中(sqlSession2是一级缓存)
Employee employee2 = mapper2.selectEmpByEmpId(1);
System.out.println(employee2);
// 只要两次查询之间出现了增删改操作,二级缓存就会失效(一级缓存也会失效)
int affectedRowCount = mapper2.insertEmployee(new Employee(null, "Akame", null, "女", 15000.0));
System.out.println("affectedRowCount = " + affectedRowCount);
Employee employee3 = mapper2.selectEmpByEmpId(1);
System.out.println(employee3);
// 程序执行到这里,会将sqlSesion2中一级缓存中的数据缓存到二级缓存中
sqlSession2.close();
}
二级缓存的相关配置:
-
eviction 属性:缓存回收策略,默认的是 LRU。
- LRU(Least Recently Used):最近最少使用的,优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用)
- FIFO(First in First out):先进先出,先进入二级缓存的对象最先被淘汰
- SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象,具体算法和 JVM 的垃圾回收算法有关
- WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象,具体算法和 JVM 的垃圾回收算法有关
-
flushInterval 属性:二级缓存的刷新间隔,单位毫秒
- 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
-
size 属性:设置二级缓存中对多可存储的 Java 对象数,正整数,默认值 1024
- 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
-
readOnly 属性:只读, true/false
- true:只读缓存,多条相同的 SQL 语句执行之后返回的对象是共享的同一个,性能好,但是多线程并发可能会存在安全问题
- false:读写缓存,多条相同的 SQL 语句执行之后返回的对象是副本,调用了 clone(),性能一般,但安全
MyBatis缓存查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存。
如果一级缓存也没有命中,则查询数据库
SqlSession 关闭之后,一级缓存中的数据会写入二级缓存
四、MyBatis整合第三方缓存
4.1、MyBatis集成EhCache
集成 EaChe 是为了代替 MyBatis 自带的二级缓存,一级缓存是无法被替代的。MyBatis 对外提供了接口,也可以集成第三方的缓存组件,比如 EhCache、Memcache 等。EhCache 是 Java 写的,Memcache 是 C语言 写的。因此,MyBatis 集成 EhCache 较为常见。
4.2、添加配置
<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
4.3、创建EHCache的配置文件
创建 EHCache 的配置文件,配置文件名为 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="E:\Program\Java\MyBatis\ehcache"/>
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
EHCache配置文件说明:
属性名 | 是否必须 | 作用 |
---|---|---|
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(先进先出) |
4.4、设置二级缓存的类型
在 MyBatis 的映射文件中设置二级缓存的类型。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
标签:11,salary,缓存,age,empId,sex,MyBatis,public
From: https://www.cnblogs.com/nanoha/p/16812777.html