MyBatis缓存机制
1、缓存机制简介
- MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
- MyBatis系统中默认定义了两级缓存,即一级缓存和二级缓存。
- 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。
2、一级缓存
一级缓存(local cache),即本地缓存,作用域默认为sqlSession。当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空。
本地缓存不能被关闭,但可以调用 clearCache() 来清空本地缓存,或者改变缓存的作用域。
在mybatis3.1之后,可以配置本地缓存的作用域,即在mybatis.xml中配置。
一级缓存失效有四种情况:
- 不同的SqlSession对应不同的一级缓存。
- 同一个SqlSession但是查询条件不同。
- 同一个SqlSession两次查询期间执行了任何一次增删改操作。
- 同一个SqlSession两次查询期间手动清空了缓存。
现在根据https://perfectcode.top/2020/12/24/%E6%90%AD%E5%BB%BAMyBatis/搭建一个MyBatis项目,并新建测试类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class MybatisTest {
public static void main(String[] args) throws IOException {
//1.根据全局配置文件创建一个sqlSessionFactory
//SqlSessionFactory是SqlSession工厂,负责创建SqlSession对象
//SqlSession:sql会话(代表和数据库的一次会话)
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//2.获取和数据库的一次会话:getConnection()
SqlSession sqlSession = sqlSessionFactory.openSession();
//3.使用SqlSession操作数据库,先获取到dao接口实现
UserDao mapper = sqlSession.getMapper(UserDao.class);
//第一次查询
List<User> allUsers = mapper.getAllUsers();
System.out.println(allUsers);
System.out.println("*******************");
//可调用sqlSession.clearCache()来手动清空缓存
//在此sqlSession进行第二次相同查询
List<User> allUsers1 = mapper.getAllUsers();
System.out.println(allUsers1);
System.out.println(allUsers == allUsers1);//true
}
}执行结果如下,可见第二次查询是从一级缓存中获取的:
3、二级缓存
二级缓存(second level cache),全局作用域缓存。
二级缓存默认不开启,需要手动配置。
MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口。
二级缓存在 SqlSession 关闭或提交之后才会生效。
使用步骤:
①全局配置文件中开启二级缓存(
)。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<configuration>
<!--resourse:引用类路径资源-->
<!--url:网络或磁盘资源-->
<properties resource="jdbc.properties"></properties>
<settings>
<!--开启驼峰命名-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
<environments default="development">
<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>
<!--resource表示从类路径下找资源-->
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>②需要使用二级缓存的映射文件处使用cache配置缓存
。 1
2
3
4
5
6
7
8
9
10
11
12
<mapper namespace="com.example.dao.UserDao">
<!--开启二级缓存-->
<cache></cache>
<select id="getAllUsers" resultType="com.example.bean.User" >
SELECT * FROM t_users;
</select>
</mapper>③注意:POJO需要实现Serializable接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38public class User implements Serializable {
private Integer id ;
private String userName ;
private String password ;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}④新建测试类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public class MybatisTest {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserDao mapper1 = sqlSession1.getMapper(UserDao.class);
UserDao mapper2 = sqlSession2.getMapper(UserDao.class);
List<User> allUsers = mapper1.getAllUsers();
System.out.println(allUsers);
sqlSession1.close();
List<User> allUsers1 = mapper2.getAllUsers();
System.out.println(allUsers1);
sqlSession2.close();
}
}测试结果:
其中,命中率0.0表示第一次去缓存中找不到数据,即0/1=0.0;命中率0.5表示第二次去缓存中找到数据,即1/2=0.5。
4、缓存的查询顺序
当MyBatis同时开启一级缓存和二级缓存时:
- 二级缓存中的数据是一级缓存中关闭就有了,且不会出现一级缓存和二级缓存中有相同数据的情况。
- 当查询数据时,任何时候都是先查二级缓存,如果其中没有数据,再去查一级缓存,如果一级缓存也没有就去查询数据库,数据库查询结果放在一级缓存。
①为了测试需要,在前面的基础上,在UserDao和UserMapper.xml中新增不同的查询接口方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<mapper namespace="com.example.dao.UserDao">
<!--开启二级缓存-->
<cache></cache>
<select id="getAllUsers" resultType="com.example.bean.User" >
SELECT * FROM t_users;
</select>
<select id="getUserById" resultType="com.example.bean.User" >
SELECT * FROM t_users where id = #{id};
</select>
</mapper>1
2
3
4
5public interface UserDao {
List<User> getAllUsers();
User getUserById(int id);
}②新建测试类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31public class MybatisTest {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserDao mapper1 = sqlSession1.getMapper(UserDao.class);
List<User> allUsers1 = mapper1.getAllUsers();
System.out.println(allUsers1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserDao mapper2 = sqlSession2.getMapper(UserDao.class);
//从二级缓存中拿数据
List<User> allUsers2 = mapper2.getAllUsers();
System.out.println(allUsers2);
List<User> allUsers3 = mapper2.getAllUsers();
System.out.println(allUsers3);
System.out.println("******************");
//此时二级缓存和一级缓存都没有此数据,就会去查询数据库并把数据放在一级缓存
User user1 = mapper2.getUserById(1);
System.out.println(user1);
//此时先去二级缓存查看数据,发现没有user2的信息,就去一级缓存拿数据
User user2 = mapper2.getUserById(1);
System.out.println(user2);
sqlSession2.close();
}
}测试结果:
其中,命中率0.0表示第一次去缓存中找不到数据,即0/1=0.0;命中率0.5(第一个0.5)表示第二次去二级缓存中找到数据(第一次找到数据),即1/2=0.5;命中率0.6666666表示第三次去二级缓存中找到数据(第二次找到数据),即2/3=0.6666666;命中率0.5(第二个0.5)表示第四次去缓存中找不到数据,即2/4=0.5;命中率0.4表示第五次去二级缓存中找不到数据,即2/5=0.4。