开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 16 天,点击查看活动详情
我们知道mybatis的一级缓存及二级缓存,今天我们着重讲解一下 mybatis的二级缓存机制
一级缓存和二级缓存的区别
- 一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的是 SqlSession 之间的缓存数据区(HashMap)是互相不影响
- 二级缓存 是 Mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的
- Mybatis 默认没有开启二级缓存 需要在 setting 全局参数中配置开启二级缓存。
- 二级缓存开启后 默认所有操作都使用缓存
- 开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库
Mybatis 二级缓存是多个 SqlSession 共享的,其作用域是 mapper 的同一个 namespace,不同 的 sqlSession 两次执行相同 namespace 下的 sql 语句且向 sql 中传递参数也相同即最终执行 相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从 缓存中获取数据将不再从数据库查询,从而提高查询效率。
1.二级缓存配置
1.1 二级缓存的原理
二级缓存有过期时间,但没有后台线程进行检测 需要注意的是,并不是key-value的过期时间,而是这个cache的过期时间,是flushInterval,意味着整个清空缓存cache,所以不需要后台线程去定时检测。
每当存取数据的时候,都有检测一下cache的生命时间,默认是1小时,如果这个cache存活了一个小时,那么将整个清空一下。
4、当 Mybatis 调用 Dao 层查询数据库时,先查询二级缓存,二级缓存中无对应数据,再去查询一级缓存,一级缓存中也没有,最后去数据库查找。
1.2 二级缓存的配置
参数cacheEnabled,这个参数是二级缓存的全局开关,默认值是true,如果把这个设置为false,即使后面的二级缓存配置,也不会生效。
接下来测试一级/二级缓存
Service方法 selectUserCacheTwo
UserInfoPO selectUserCacheTwo(String uid);
=======================================
@Override
public UserInfoPO selectUserCacheTwo(String uid) {
return mapper.selectUserCacheTwo(uid);
}
================================================
@Select("select * from user_info where id =#{uid}")
UserInfoPO selectUserCacheTwo(@Param(value = "uid")String uid);
1.3 TestController 测试方法
1.测试方法 加事务 ,同一个SqlSession 走一级缓存测试
@RequestMapping("/temp/cache2")
@ResponseBody
@Transactional
public Object cache2() {
UserInfoPO user1 = userService.selectUserCacheTwo("11");
//对user1的属性进行修改,先不入库
user1.setAge(123);
user1.setUserName("456");
user1.setAddress("78910");
log.info("user1==" + JSONUtil.toJsonStr(user1));
//再次查询user2
// 看看 user2的数值,按道理我修改的是user1
//user2 是从db中取出来的,应该还是 原来的值,我们看下改变了每
UserInfoPO user2 = userService.selectUserCacheTwo("11");
log.info("user2==" + JSONUtil.toJsonStr(user2));
log.info("result:" + (user1 == user2));
return "pong";
}
结果
- 走了一级缓存 两个都是修改过后的 "address":"78910"
- user1和user2同一个内存地址,修改了一个另一个同样修改
- user1 == user2 result:true
2023-02-16 23:01:01.868 INFO 15608 --- [nio-8800-exec-3] c.j.tdmybatis.controller.TestController : user1=={"address":"78910","modtime":0,"goods":"{\"deptId\": 3, \"deptName\": \"部门3\", \"deptLeaderId\": 4}","userName":"456","addtime":0,"id":11,"age":123}
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1ab79f3c] from current transaction
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1ab79f3c]
2023-02-16 23:01:01.869 INFO 15608 --- [nio-8800-exec-3] c.j.tdmybatis.controller.TestController : user2=={"address":"78910","modtime":0,"goods":"{\"deptId\": 3, \"deptName\": \"部门3\", \"deptLeaderId\": 4}","userName":"456","addtime":0,"id":11,"age":123}
2023-02-16 23:01:01.869 INFO 15608 --- [nio-8800-exec-3] c.j.tdmybatis.controller.TestController : result:true
2.测试方法 去掉事务, 不同SqlSession 不走一级缓存
把 @Transactional 去掉,不同的SqlSession
/**
* 探活接口
*/
@RequestMapping("/temp/cache2")
@ResponseBody
public Object cache2() {}
执行结果
- 没走 一级缓存,不同的SqlSession 一个修改后"address":"78910" 一个DB修改前"address":"深圳"
- user1和user2 不同的对象 ,不同的内存地址,修改了一个, 不会影响另外一个
- user1 == user2 result:false
2023-02-16 23:05:49.160 INFO 2312 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : user1=={"address":"78910","modtime":0,"goods":"{\"deptId\": 3, \"deptName\": \"部门3\", \"deptLeaderId\": 4}","userName":"456","addtime":0,"id":11,"age":123}
Creating a new SqlSession
2023-02-16 23:05:49.162 INFO 2312 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : user2=={"address":"深圳","modtime":0,"goods":"{\"deptId\": 3, \"deptName\": \"部门3\", \"deptLeaderId\": 4}","addtime":0,"id":11,"age":49}
2023-02-16 23:05:49.162 INFO 2312 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : result:false
1.4 配置二级缓存
二级缓存是全局性地开启或关闭, 所有映射器配置文件中已配置的任何缓存
- application.properties中配置
#开启二级缓存
mybatis.configuration.cache-enabled=true
2.UserMapper 类上加注解 @CacheNamespace
@CacheNamespace
public interface UserInfoMapper extends BaseMapper<UserInfoPO> {}
3.UserInfoPO 对象 实现seriable
public class UserInfoPO implements Serializable {}
UserInfoPO 对象要实现 seriable 为什么 ?
为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们再取出这个缓存的话,就需要反序列化。所以 MyBatis 的所有 pojo 类都要去实现 Serializable 序列化接口, 否则报错
[Request processing failed; nested exception is org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: com.jzj.tdmybatis.domain.po.UserInfoPO] with root cause
4.直接访问 cache2方法
@RequestMapping("/temp/cache2")
@ResponseBody
public Object cache2() {
UserInfoPO user1 = userService.selectUserCacheTwo("11");
//对user1的属性进行修改,先不入库
user1.setAge(123);
user1.setUserName("456");
user1.setAddress("78910");
log.info("user1==" + JSONUtil.toJsonStr(user1));
//再次查询user2
// 看看 user2的数值,按道理我修改的是user1
//user2 是从db中取出来的,应该还是 原来的值,我们看下改变了每
UserInfoPO user2 = userService.selectUserCacheTwo("11");
log.info("user2==" + JSONUtil.toJsonStr(user2));
log.info("result:" + (user1 == user2));
return "pong";
}
2.二级缓存结果
2.1 cache-enabled=false测试关闭二级缓存
我们先把 mybatis.configuration.cache-enabled=false 改为false 试一下 原来的效果 执行
curl 127.0.0.1:8800/temp/cache2
- 查询了 两次SQL, 两个不同的SqlSession
- 第一次 select * from user_info where id =? Total: 1
- 第二次 select * from user_info where id =? Total: 1
- user1 改完后 "address":"78910"
- user2 查完后 "address":"深圳"
- 两个不同的对象, 两个不同内存地址
==> Preparing: select * from user_info where id =?
==> Parameters: 11(String)
<== Columns: id, user_id, user_name, age, address, order_ids, goods, sort_order, is_del, is_del2, addtime, modtime
<== Row: 11, 126, kk, 49, 深圳, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@393d7ce3]
2023-02-16 23:20:30.254 INFO 5776 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : user1=={"address":"78910","modtime":0,"goods":"{\"deptId\": 3, \"deptName\": \"部门3\", \"deptLeaderId\": 4}","userName":"456","addtime":0,"id":11,"age":123}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8337e00] was not registered for synchronization because synchronization is not active
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@66fbf99b] will not be managed by Spring
==> Preparing: select * from user_info where id =?
==> Parameters: 11(String)
<== Columns: id, user_id, user_name, age, address, order_ids, goods, sort_order, is_del, is_del2, addtime, modtime
<== Row: 11, 126, kk, 49, 深圳, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8337e00]
2023-02-16 23:20:30.256 INFO 5776 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : user2=={"address":"深圳","modtime":0,"goods":"{\"deptId\": 3, \"deptName\": \"部门3\", \"deptLeaderId\": 4}","addtime":0,"id":11,"age":49}
2023-02-16 23:20:30.256 INFO 5776 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : result:false
2.2 cache-enabled=true 测试开启二级缓存
#开启二级缓存
mybatis.configuration.cache-enabled=true
curl 127.0.0.1:8800/temp/cache2
查看结果
- 只查询了一次SQL select * from user_info where id =? Total: 1
- 创建了2次 SqlSession Creating a new SqlSession ,但是只有一次SQL查询
- cache命中率 0.5 Cache Hit Ratio [com.jzj.tdmybatis.repository.mapper.UserInfoMapper]: 0.5
- user1和user2 依旧不是一个对象 result:false, 因为为二级缓存的是数据,并不是对象。而 user1 与 user2 是两个对象,所以地址值当然也不相等。
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d263d4e] was not registered for synchronization because synchronization is not active
Cache Hit Ratio [com.jzj.tdmybatis.repository.mapper.UserInfoMapper]: 0.0
2023-02-16 23:27:00.152 INFO 22812 --- [nio-8800-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2adf58a7] will not be managed by Spring
==> Preparing: select * from user_info where id =?
==> Parameters: 11(String)
<== Columns: id, user_id, user_name, age, address, order_ids, goods, sort_order, is_del, is_del2, addtime, modtime
<== Row: 11, 126, kk, 49, 深圳, <<BLOB>>, <<BLOB>>, 0, 0, 0, 0, 0
<== Total: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d263d4e]
2023-02-16 23:27:00.198 INFO 22812 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : user1=={"address":"78910","modtime":0,"goods":"{\"deptId\": 3, \"deptName\": \"部门3\", \"deptLeaderId\": 4}","userName":"456","addtime":0,"id":11,"age":123}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16d7bf25] was not registered for synchronization because synchronization is not active
Cache Hit Ratio [com.jzj.tdmybatis.repository.mapper.UserInfoMapper]: 0.5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16d7bf25]
2023-02-16 23:27:00.200 INFO 22812 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : user2=={"address":"深圳","modtime":0,"goods":"{\"deptId\": 3, \"deptName\": \"部门3\", \"deptLeaderId\": 4}","addtime":0,"id":11,"age":49}
2023-02-16 23:27:00.200 INFO 22812 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : result:false
至此,我们已经了解了二级缓存的配置及二级缓存的使用方法
实际业务中二级缓存针对大多数的业务系统都不推荐使用,因为业务系统数据操作比较频繁、自带的二级缓存性能也不是很高。二级缓存很难起到实际的作用,推荐使用redis或者Encache来实现第三方缓存