Mybatis 的缓存
# 190.Mybatis 的缓存
缓存,就是存在于内存中的临时数据。使用缓存,可以减少和数据库的交互次数,提高执行效率。
# 什么数据需要缓存?
什么样的数据能使用缓存,什么样的数据不能使用?
适用于缓存的情况:经常查询并且不经常改变的,或数据的正确与否对最终结果影响不大的情况
不适用于缓存的情况:经常改变的数据,或数据的正确与否对最终结果影响很大的。例如:商品的库存,银行的汇率,股市的牌价。
Mybatis 中存在一级缓存和二级缓存。
# Mybatis 的一级缓存
一级缓存:它指的是 Mybatis 中 SqlSession 对象的缓存。
- 当执行查询之后,查询的结果会同时存入到 SqlSession 为我们提供的一块区域中,该区域的结构是一个 Map
- 当再次查询同样的数据,Mybatis 会先去 sqlsession 中查询是否有,有的话直接拿出来用,没有则查数据库
- 当 SqlSession 对象消失时,mybatis 的一级缓存也就消失了
为了方便演示,我们使用的代码是入门案例的代码,读者可以切换到 demo1,在其基础上新建一个分支后开发。
我们新建查询一个的方法:在 IUserDao.java 接口中新增:
/**
* 根据id查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
2
3
4
5
6
修改 IUserDao.xml,增加如下代码:
<!-- 根据ID查询用户 -->
<select id="findById" parameterType="INT" resultType="com.peterjxl.domain.User">
select * from user where id = #{uid}
</select>
2
3
4
测试
@Test
public void testFirstLevelCache(){
User user = userDao.findById(41);
System.out.println("user: " + user);
User user2 = userDao.findById(41);
System.out.println("user2: " + user2);
System.out.println("user2 == user ? : " + (user2 == user));
}
2
3
4
5
6
7
8
9
10
我们试着运行,可以看到只查询了一次,并且两个对象是同一个:
而如果 Session 关闭了,那么缓存就会被清理:
@Test
public void testFirstLevelCache(){
User user = userDao.findById(41);
System.out.println("user: " + user);
session.close();
session = factory.openSession();
userDao = session.getMapper(IUserDao.class);
User user2 = userDao.findById(41);
System.out.println("user2: " + user2);
System.out.println("user2 == user ? : " + (user2 == user));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
运行结果:
除此之外,sqlSession 还存在一个 clearCache 的方法,可以清理缓存。
session.clearCache();
# 触发清空一级缓存的情况
如果数据库的数据,和缓存里的数据不一致了,那么 Mybatis 是如何更新缓存的呢?我们来举个例子。
修改 IUserDao.java。增加 updateUser 方法
package com.peterjxl.dao;
import com.peterjxl.domain.User;
import java.util.List;
public interface IUserDao {
List<User> findAll();
void saveUser(User user);
void updateUser(User user);
}
2
3
4
5
6
7
8
9
在 IUserDao.xml 里增加一个 update 标签
<update id="updateUser" parameterType="com.peterjxl.domain.User">
update user
set username=#{username}, address=#{address}, sex=#{sex}, birthday=#{birthday}
where id=#{id}
</update>
2
3
4
5
新增测试方法
@Test
public void testClearCache(){
// 1. 根据 id 查询用户信息
User user = userDao.findById(41);
System.out.println("user: " + user);
// 2. 更新用户信息
user.setUsername("update user clear cache");
user.setAddress("克莱登大学");
userDao.updateUser(user);
// 3. 再次查询id 为41的用户
User user2 = userDao.findById(41);
System.out.println("user2: " + user2);
System.out.println("user2 == user ? : " + (user2 == user));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
运行结果:可以看到发起了两次查询:
这是因为当调用 SqlSession 的修改,添加,删除,commit()
,close()
等方法时,就会清空一级缓存。
# Mybatis 的二级缓存
二级缓存:指的是 Mybatis 中 SqlSessionFactory 对象的缓存。由同一个 SqlSessionFactory 对象创建的 SqlSession 共享其缓存。使用步骤:
- 让 Mybatis 框架支持二级缓存(在 SqlMapConfig.xml 中配置)
- 让当前的映射文件支持二级缓存(在 IUserDao.xml 中配置)
- 让当前的操作支持二级缓存(在 select 标签中配置)
# 新增测试类
这里我们将 SqlSession
和 IUserDao
对象,不初始化,也不单独定义成员变量:
package com.peterjxl.test;
import com.peterjxl.dao.IUserDao;
import com.peterjxl.domain.User;
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;
import java.util.List;
public class SecondLevelCacheTest {
private InputStream in;
private SqlSessionFactoryBuilder builder;
private SqlSessionFactory factory;
@Before
public void init() throws IOException {
in = Resources.getResourceAsStream("SqlMapConfig.xml");
builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
}
@After
public void destory() throws IOException {
in.close();
}
}
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
新增测试方法:
@Test
public void testSecondLevelCache(){
SqlSession sqlSession1 = factory.openSession();
IUserDao dao1 = sqlSession1.getMapper(IUserDao.class);
User user1 = dao1.findById(41);
System.out.println(user1);
sqlSession1.close();
SqlSession sqlSession2 = factory.openSession();
IUserDao dao2 = sqlSession2.getMapper(IUserDao.class);
User user2 = dao2.findById(41);
System.out.println(user2);
sqlSession2.close();
System.out.println("user1 == user2 ? : " + (user1 == user2));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可以看到还是查询了两次,这是因为我们还没配置二级缓存:
# 在主配置文件中配置二级缓存
在官网文档:mybatis – MyBatis 3 | 配置 (opens new window) 中,有提到如何开启缓存:
可以看到默认值为 true,其实不配置也行,这里就是为了让大伙知道这个配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2
3
# 让当前的映射文件支持二级缓存(在 IUserDao.xml 中配置)
只需加一个 <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">
<mapper namespace="com.peterjxl.dao.IUserDao">
<cache/>
// 省略其他配置。。。。。
</mapper>
2
3
4
5
6
7
8
# 让当前的操作支持二级缓存(在 select 标签中配置)
在 select 标签里,新增属性 useCache="true"
<!-- 根据ID查询用户 -->
<select id="findById" parameterType="INT" resultType="com.peterjxl.domain.User" useCache="true">
select * from user where id = #{uid}
</select>
2
3
4
可以看到只查询了一次,并告诉我们命中了缓存(Cache Hit)
但是,两个对象却是不相等的:
System.out.println("user1 == user2 ? : " + (user1 == user2));
这是因为二级缓存中,内容是数据,而不是对象。
# 源码
所有代码已上传到了 GitHub (opens new window) 和 Gitee (opens new window) 上,并且创建了分支 demo18,读者可以通过切换分支来查看本文的示例代码。