一级缓存
先通过一个简单示例来看看 MyBatis 一级缓存如何起作用。在 src.mybatis.simple.mapper 包下,新建如下测试类。
public class CacheTest extends BaseMapperTest {
@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);
//对当前获取的对象重新赋值
user1.setUserName("New Name");
//再次查询获取 id 相同的用户
SysUser user2 = userMapper.selectById(1l);
//虽然我们没有更新数据库,但是这个用户名和我们 user1 重新赋值的名字相同了
Assert.assertEquals("New Name", user2.getUserName());
//不仅如何,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);
//第二个 session 获取的用户名仍然是 admin
Assert.assertNotEquals("New Name", user2.getUserName());
//这里的 user2 和 前一个 session 查询的结果是两个不同的实例
Assert.assertNotEquals(user1, user2);
//执行删除操作
userMapper.deleteById(2L);
//获取 user3
SysUser user3 = userMapper.selectById(1l);
//这里的 user2 和 user3 是两个不同的实例
Assert.assertNotEquals(user2, user3);
} finally {
//关闭 sqlSession
sqlSession.close();
}
}
}
先执行该测试输出日志,然后结合日志一起来看以上代码。输出日志如下。
在第一次执行 selectById 方法获取 SysUser 数据时,真正执行了数据库查询,得到了 user1 的结果。第二次执行获取 user2 的时候,从日志可以看到,在 “开启新的 sqlSession” 这行日志上面,只有一次查询,也就是说第二次查询并没有执行数据库操作。
从测试代码来看,获取 user1 后重新设置了 userName 的值,之后没有进行任何更新数据库的操作。在获取 user2 对象后,发现 user2 对象的 userName 值竟然和 user1 重新设置后的值一样。再往下可以发现,原来 user1 和 user2 竟然是同一个对象,之所以这样就是因为 MyBatis 的一级缓存。
MyBatis 的一级缓存存在于 SqlSession 的生命周期中,在同一个 SqlSession 中查询时,MyBatis 会把执行的方法和参数通过算法生成缓存的键值,将键值和查询结果存入一个 Map 对象中。如果同一个 SqlSession 中执行的方法和参数完全一致,那么通过算法会生成相同的键值,当 Map 缓存对象中已经存在该键值时,则会返回缓存中的对象。
缓存中的对象和我们得到的结果是同一个对象,反复使用相同参数执行同一个方法时,总是返回同一个对象,因此就会出现上面测试代码中的情况。在使用 MyBatis 的过程中,要避免在使用如上代码中的 user2 时出现的错误。我们可能以为获取的 user2 应该是数据库中的数据,却不知道 user1 的一个重新赋值会影响到 user2。如果不想让 selectById 方法使用一级缓存,可以对该方法做如下修改。
<select id="selectByid" flushCache="true" resultMap="userMap">
select * from sys_user where id=#{id}
</select>
该修改在原来方法的基础上增加了 flushCache="true",这个属性配置为 true 后,会在查询数据前清空当前的一级缓存,因此该方法每次都会重新从数据库中查询数据,此时的 user2 和 user1 就会成为两个不同的实例,可以避免上面的问题。但是由于这个方法清空了一级缓存,会影响当前 SqlSession 中所有缓存的查询,因此在需要反复查询获取只读数据的情况下,会增加数据库的查询次数,所以要避免这么使用。
在关闭第一个 SqlSession 后,又重新获取了一个 SqlSession,因此又重新查询了 user2,这时在日志中输出了数据库查询 SQL,user2 是一个新的实例,和 user1 没有任何关系。这是因为一级缓存是和 SqlSession 绑定的,只存在于 SqlSession 的生命周期中。
接下来执行了一个 deleteById 操作,然后使用相同的方法和参数获取了 user3 实例,从日志和结果来看,user3 和 user2 也是完全不同的两个对象。这是因为任何的 INSERT、UPDATE、DELETE 操作都会清空一级缓存,所以查询 user3 的时候由于缓存不存在,就会再次执行数据库查询获取数据。
关于一级缓存中的各种情况,通过上面的测试都已经介绍完了,由于一级缓存是在默默地工作,因此要避免在使用过程中由于不了解而发生觉察不到的错误。