Mybatis 提供一级缓存和二级缓存的机制。 一级缓存是 SQLSession 级别的缓存。在操作数据库时,每个 SqlSession 类的实例对象中有一个数据结构(HashMap)可以用于存储缓存数据。不同的 SqlSession 类的实例对象的数据区域(HashMap)是互不影响的。 二级缓存是 Mapper 级别的缓存,多个 SqlSession 类的实例对象操作同一个 Mapper 配置文件中的 SQL 语句,多个 SqlSession 类的实例对象可以共用二级缓存,二级缓存是跨 SqlSession 的。 Mybatis 的缓存模式如下图所示: 每个 SqlSession 类的实例对象自身有一个一级缓存,而查询同一个 Mapper 映射文件的 SqlSession 类的实例对象之间又共享同一个二级缓存。
一、一级查询缓存
一级查询缓存存在于每一个 SqlSession 类的实例对象中,当第一次查询某一个数据时,SqlSession 类的实例对象会将该数据存入一级缓存区域,在没有收到改变数据的请求之前,用户再次查询该数据,都会从缓存中获取该数据,而不是再次连接数据库进行查询。
1.1 一级缓存原理阐述
上图阐述了一个 SqlSession 类的实例对象下的一级缓存的工作原理。当第一次查询 id 为 1 的用户信息时,SqlSession 首先到一级缓存区域查询,如果没有相关数据,则从数据库查询。然后 SqlSession 将查询结果保存到一级缓存区域。在下一次查询时,如果 SqlSession 执行了 commit 操作(即执行了修改、添加和删除),则会清空它的一级缓存区域,以此来保证缓存中的信息是最新的,避免脏读现象发生。如果在这期间 SqlSession 一直没有执行 commit 操作修改数据,那么下一次查询 id 为 1 的用户信息时,SqlSession 在一级缓存中就会发现该信息,然后从缓存中获取用户信息。
1.2 一级缓存测试示例
Mybatis 默认支持一级缓存,不需要在配置文件中配置一级缓存的相关数据。 使用同一个 SqlSession 对象,对 id 为 1 的员工查询两次。
EmployeeCacheMapper.xml
<?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">
<mapper namespace="com.example.mapper.EmployeeCacheMapper">
<select id="getEmpById" resultType="com.example.pojo.Employee">
select * from tbl_employee where id = #{id}
</select>
</mapper>
EmployeeCacheMapper.java
package com.example.mapper;
import com.example.pojo.Employee;
public interface EmployeeCacheMapper {
public Employee getEmpById(Integer id);
}
测试方法:
package com.example;
import com.example.mapper.EmployeeCacheMapper;
import com.example.pojo.Employee;
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.testng.annotations.Test;
import java.io.IOException;
import java.io.InputStream;
public class MybatisCacheTest {
public SqlSessionFactory getSqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
@Test
public void test01() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
EmployeeCacheMapper mapper = sqlSession.getMapper(EmployeeCacheMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
Employee emp02 = mapper.getEmpById(1);
System.out.println(emp02);
System.out.println(emp01 == emp02);
} finally {
sqlSession.close();
}
}
}
控制台结果:
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Preparing: select * from tbl_employee where id = ?
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] <== Total: 1
Employee{id=1, lastName='tomcat', email='tom@123.com', gender='0', department=null}
Employee{id=1, lastName='tomcat', email='tom@123.com', gender='0', department=null}
true
当第一次查询 id 为 1 的员工信息时,执行了 SQL 语句,而第二次查询 id 为 1 的员工信息时,没有任何的日志输出。而对于两次取出的员工对象的比对,也证明了第二次数据不是从数据库查询出来的,是从一级缓存中获取的。
1.3 一级缓存失效的四种情况
1、SqlSession 不同
测试语句如下:
@Test
public void test02() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
EmployeeCacheMapper mapper = sqlSession.getMapper(EmployeeCacheMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
EmployeeCacheMapper mapper1 = sqlSession1.getMapper(EmployeeCacheMapper.class);
Employee emp02 = mapper1.getEmpById(1);
System.out.println(emp02);
System.out.println(emp01 == emp02);
} finally {
sqlSession.close();
}
}
控制台结果:
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Preparing: select * from tbl_employee where id = ?
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] <== Total: 1
Employee{id=1, lastName='tomcat', email='tom@123.com', gender='0', department=null}
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Preparing: select * from tbl_employee where id = ?
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] <== Total: 1
Employee{id=1, lastName='tomcat', email='tom@123.com', gender='0', department=null}
false
2、SqlSession 相同,查询条件不同
分别查询 id 为 1 和 id 为 2 个员工信息 测试方法:
@Test
public void test03() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
EmployeeCacheMapper mapper = sqlSession.getMapper(EmployeeCacheMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
Employee emp02 = mapper.getEmpById(2);
System.out.println(emp02);
System.out.println(emp01 == emp02);
} finally {
sqlSession.close();
}
}
控制台结果:
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Preparing: select * from tbl_employee where id = ?
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] <== Total: 1
Employee{id=1, lastName='tomcat', email='tom@123.com', gender='0', department=null}
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Preparing: select * from tbl_employee where id = ?
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Parameters: 2(Integer)
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] <== Total: 1
Employee{id=2, lastName='jerry', email='jerry@123.com', gender='0', department=null}
false
3、SqlSession 相同,两次查询之间执行了增删改操作
增删改操作可能会对数据有影响 EmployeeCacheMapper.xml:
<insert id="addEmp" databaseId="mysql" parameterType="employee" useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee (id, last_name, email, gender)
values (#{id}, #{lastName}, #{email}, #{gender})
</insert>
EmployeeCacheMapper.java
package com.example.mapper;
import com.example.pojo.Employee;
public interface EmployeeCacheMapper {
public void addEmp(Employee employee);
}
测试方法:
@Test
public void test04() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
EmployeeCacheMapper mapper = sqlSession.getMapper(EmployeeCacheMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
Employee employee = new Employee();
employee.setLastName("meichaofeng");
employee.setEmail("meichaofeng@123.com");
employee.setGender("0");
mapper.addEmp(employee);
Employee emp02 = mapper.getEmpById(1);
System.out.println(emp02);
System.out.println(emp01 == emp02);
} finally {
sqlSession.close();
}
}
控制台结果:
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Preparing: select * from tbl_employee where id = ?
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] <== Total: 1
Employee{id=1, lastName='tomcat', email='tom@123.com', gender='0', department=null}
[main] [com.example.mapper.EmployeeCacheMapper.addEmp]-[DEBUG] ==> Preparing: insert into tbl_employee (id, last_name, email, gender) values (?, ?, ?, ?)
[main] [com.example.mapper.EmployeeCacheMapper.addEmp]-[DEBUG] ==> Parameters: null, meichaofeng(String), meichaofeng@123.com(String), 0(String)
[main] [com.example.mapper.EmployeeCacheMapper.addEmp]-[DEBUG] <== Updates: 1
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Preparing: select * from tbl_employee where id = ?
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] <== Total: 1
Employee{id=1, lastName='tomcat', email='tom@123.com', gender='0', department=null}
false
4、SqlSession 相同,但手动清除了一级缓存
测试方法:
@Test
public void test05() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
EmployeeCacheMapper mapper = sqlSession.getMapper(EmployeeCacheMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
sqlSession.clearCache();
Employee emp02 = mapper.getEmpById(1);
System.out.println(emp02);
System.out.println(emp01 == emp02);
} finally {
sqlSession.close();
}
}
控制台结果:
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Preparing: select * from tbl_employee where id = ?
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] <== Total: 1
Employee{id=1, lastName='tomcat', email='tom@123.com', gender='0', department=null}
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Preparing: select * from tbl_employee where id = ?
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] ==> Parameters: 1(Integer)
[main] [com.example.mapper.EmployeeCacheMapper.getEmpById]-[DEBUG] <== Total: 1
Employee{id=1, lastName='tomcat', email='tom@123.com', gender='0', department=null}
false
|