1.一级缓存
首先看一段代码,猜测一下对应的结果
@Test
public void testL1Cache(){
//获取 sqlSession
SqlSession sqlSession = getSqlSession();
SysUser user1 = null;
try {
//获取 UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用 selectById 方法,查询 id = 1 的用户
user1 = userMapper.selectById(1l);
System.out.println(user1);
//对当前获取的对象重新赋值
user1.setUserName("New Name");
//再次查询获取 id 相同的用户
SysUser user2 = userMapper.selectById(1l);
System.out.println(user2);
//虽然我们没有更新数据库,但是这个用户名和我们 user1 重新赋值的名字相同了
Assert.assertEquals("New Name", user2.getUserName());
System.out.println(user1==user2);
//不仅如此,user2 和 user1 完全就是同一个实例
Assert.assertEquals(user1, user2);
} finally {
//关闭当前的 sqlSession
sqlSession.close();
}
System.out.println("开启新的 sqlSession");
//开始另一个新的 session
sqlSession = getSqlSession();
try {
//获取 UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用 selectById 方法,查询 id = 1 的用户
SysUser user2 = userMapper.selectById(1l);
System.out.println(user2);
//第二个 session 获取的用户名仍然是 admin
Assert.assertNotEquals("New Name", user2.getUserName());
//这里的 user2 和 前一个 session 查询的结果是两个不同的实例
Assert.assertNotEquals(user1, user2);
System.out.println(user2==user1);
//执行删除操作
userMapper.deleteById(2L);
//获取 user3
SysUser user3 = userMapper.selectById(1l);
System.out.println(user3);
//这里的 user2 和 user3 是两个不同的实例
Assert.assertNotEquals(user2, user3);
System.out.println(user2==user3);
} finally {
//关闭 sqlSession
sqlSession.close();
}
}
接下来逐个进行解释:
首先这段代码我们查询了两次数据库,但是只有一次对数据库的查询日志打印,而且最后我们发现user1和user2甚至都是一个对象,这就是因为Mybatis的一级缓存,它存在于SqlSession的生命周期中,在同一个SqlSession中查询时,Mybatis会把执行的方法和参数通过算法生成缓存对应的键值,将键值和查询结果放入到一个Map对象中。所以user2和user1是同一个key的value值。我们如果不想要这种情况,可以做一些修改即可
<!--flushCache="true"在查询前会清空一级缓存-->
<select id="selectById" resultMap="userMap" flushCache="true">
select * from sys_user where id= #{id}
</select>
配置了这个属性,每次都会是先清空一级缓存,这样每次拿到的对象都是不同的实例。
接下来是第二个user2和user1却不相同,这是因为第一个SqlSession已经关闭了,而一级缓存和SqlSession是绑定的,所以user2和user1当然不相同
最后一个中我们可以看到user2和user3也不相同,这是为啥呢?我们注意到,我们在查询之前先执行了一个delete的代码,这就是原因。任何的update、insert、delete操作都会清空一级缓存,所以user3和user2也不相等。
2.二级缓存
先上代码
@Test
public void testL2Cache(){
//获取 sqlSession
SqlSession sqlSession = getSqlSession();
SysUser user1 = null;
try {
//获取 UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用 selectById 方法,查询 id = 1 的用户
user1 = userMapper.selectById(1l);
System.out.println(user1);
//对当前获取的对象重新赋值
user1.setUserName("New Name");
//再次查询获取 id 相同的用户
SysUser user2 = userMapper.selectById(1l);
System.out.println(user2);
System.out.println(user1==user2);
//虽然我们没有更新数据库,但是这个用户名和我们 user1 重新赋值的名字相同了
Assert.assertEquals("New Name", user2.getUserName());
//不仅如何,user2 和 user1 完全就是同一个实例
System.out.println(user1);
//Assert.assertNotEquals(user1, user2);
} finally {
//关闭当前的 sqlSession
sqlSession.close();
}
System.out.println("开启新的 sqlSession");
//开始另一个新的 session
sqlSession = getSqlSession();
try {
//获取 UserMapper 接口
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//调用 selectById 方法,查询 id = 1 的用户
SysUser user2 = userMapper.selectById(1l);
//第二个 session 获取的用户名仍然是 admin
Assert.assertEquals("New Name", user2.getUserName());
//这里的 user2 和 前一个 session 查询的结果是两个不同的实例
Assert.assertNotEquals(user1, user2);
//获取 user3
SysUser user3 = userMapper.selectById(1l);
//这里的 user2 和 user3 是两个不同的实例
Assert.assertNotEquals(user2, user3);
} finally {
//关闭 sqlSession
sqlSession.close();
}
}
注意对应的实体对象需要实现Serializable
public class SysUser implements Serializable {
private static final long serialVersionUID = -328602757171077630L;
//...
}
同时对应的mapper.xml也需要加入</cache>的配置
运行截图
日志中存在好几条以 Cache Hit Ratio 开头的语句 ,这行日志后面输出的值为当前执行方法的缓存命中率. 在测试第一部分中,第一次查询获取 user1的时候由于没有缓存,所以执行了数据库查询,在第二个查询获取 user2 的时候, user2 user1 是完全相同的实例,这 里使用是一级缓存,所以返回同1个实例.
调用 close 方法关闭 SqlSession 时, SqlSession 才会保存查询数据到二级缓存中 在这之后二级缓存才有了缓存数据 所以可以看到在第 一部分的两次查询时,命中率都是 0
在第二部分测试代码中,再次获取 user2 时,日志中并没有输出数据库查询,而是输出了 命中率,这时的命中率是 0.3333333333333333 这是第三 次查询,并且得到了缓存的值,因此 该方法 共被请求了3 次,有1次命中,所以命中率就是3分之一。 后面再获取 user3 的时候, 就是4次请求, 2次命中,命中率为 0.5 。并且因为可读写缓存的缘故, user2 user3 都是 反序列化得到的结果 ,所以它们不是相同的实例 在这 一部分,这两个实例是读写安全的,其 属性不会互相影响。
图片来源于https://www.cnblogs.com/happyflyingpig/p/7739749.html
二级缓存失效的条件:
1.第一次SqlSession 未提交:SqlSession 在未提交的时候,SQL 语句产生的查询结果还没有放入二级缓存中,这个时候 SqlSession2 在查询的时候是感受不到二级缓存的存在的。
2.更新对二级缓存影响:与一级缓存一样,更新操作很可能对二级缓存造成影响。