文章目录
- 一级缓存
- 一级缓存默认开启,无法关闭
- 一级缓存的禁用
- 增删改操作会清空一级缓存
- 二级缓存
- 默认禁用二级缓存
- 开启二级缓存
- 自定义二级缓存
- 与ehcache集成
- 与Redis集成
- 注意
- useCache 和 flushCache的说明
- 测试代码
mybatis缓存核心代码:
```java
// executor 是cachingExecutor
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
// cachingExecutor中的query方法
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// 有缓存直接使用缓存,没有则查询DB
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
一级缓存
一级缓存默认开启,无法关闭
@Test
public void defaultLocalCache(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
List<UserLogin> list = mapper.queryByNameLocalCache("核弹");
for (UserLogin e : list) {
System.out.println(e);
}
System.out.println("\n========================================\n");
List<UserLogin> list2 = mapper.queryByNameLocalCache("核弹");
for (UserLogin e : list2) {
System.out.println(e);
}
}
第二次查询直接使用缓存,查询的结果使用相同的引用
一级缓存的禁用
/**
* 本地缓存无法通过设置来禁用,
* 可以使用 flushCache 强制刷新,来达到与禁用缓存相同的效果
* <select id="queryByNameNoLocalCache" resultMap="BaseMap" flushCache="true">
*/
@Test
public void disableLocalCache(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
List<UserLogin> list = mapper.queryByNameNoLocalCache("核弹");
for (UserLogin e : list) {
System.out.println(e);
}
System.out.println("\n===========================================\n");
List<UserLogin> list2 = mapper.queryByNameNoLocalCache("核弹");
for (UserLogin e : list2) {
System.out.println(e);
}
}
每次查询都会flush cache,重新查询DB,所以测试执行两次DB查询,查询结果是完全不同的对象
增删改操作会清空一级缓存
@Test
public void queryWithSave(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
List<UserLogin> list = mapper.queryByNameLocalCache("核弹");
for (UserLogin e : list) {
System.out.println(e);
}
UserLogin save = new UserLogin("核弹", "淮南", "172.0.0.2");
mapper.save(save);
System.out.println("\n========================\n");
List<UserLogin> list2 = mapper.queryByNameLocalCache("核弹");
for (UserLogin e : list2) {
System.out.println(e);
}
}
仍然查询两次数据库源码分析:
- 三个方法底层都是调用DefaultSQLSession#update方法
- 执行器executor的update方法会清空本地缓存
二级缓存
默认禁用二级缓存
/**
* 默认禁用二级缓存
*/
@Test
public void defaultDisableLevel2Cache(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
List<UserLogin> list = mapper.queryByNameLocalCache("核弹");
for (UserLogin e : list) {
System.out.println(e);
}
System.out.println("\n==================SQLSession中=================\n");
List<UserLogin> oneMore = mapper.queryByNameLocalCache("核弹");
for (UserLogin e : oneMore) {
System.out.println(e);
}
System.out.println("\n==================新的SQLSession=================\n");
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserLoginMapper mapper2 = sqlSession2.getMapper(UserLoginMapper.class);
List<UserLogin> list2 = mapper2.queryByNameLocalCache("核弹");
for (UserLogin e : list2) {
System.out.println(e);
}
}
开启二级缓存
mybatis全局配置 cache-enabled: true 或 标签
具体的statement 使用 useCache = true,使本地一级缓存变为二级缓存(即作用域由SQLSession提示为namespace)
/**
* <cache/><!--开启全局缓存-->
* <select id="queryByNameLevel2Cache" resultMap="BaseMap" useCache="true">
* 启用二级缓存(作用域为namespace)
*/
@Test
public void enableLevel2Cache(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
List<UserLogin> list = mapper.queryByNameLevel2Cache("核弹");
for (UserLogin e : list) {
System.out.println(e);
}
System.out.println("\n========相同的SQLSession=======\n");
List<UserLogin> oneMore = mapper.queryByNameLevel2Cache("核弹");
for (UserLogin e : oneMore) {
System.out.println(e);
}
// session如果不关闭,二级缓存无法启用
sqlSession.close();
System.out.println("\n========新的SQLSession=======\n");
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserLoginMapper mapper2 = sqlSession2.getMapper(UserLoginMapper.class);
List<UserLogin> list2 = mapper2.queryByNameLevel2Cache("核弹");
for (UserLogin e : list2) {
System.out.println(e);
}
}
只查询了一次数据库二级缓存中的对象与一级缓存中的对象引用不同,二级缓存涉及到对象的序列化与反序列化,所以得到的是新的对象。
自定义二级缓存
通过mybatis源码可以看到,mybatis内部的几种二级缓存实现都是继承Cache接口,这是实现自定义缓存的关键.
无论是mybatis默认的二级缓存实现,还是与第三方集成(如 Redis,ehcache),因为实现了同一接口,其基本流程都是类似的: 将一次查询结果作为value,将查询参数,namespace等组合生成key,不同的二级缓存实现,k-v存储的逻辑不同.
与ehcache集成
- 导入依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
- 指定二级缓存的具体实现,且开启二级缓存
<!--<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>-->
<select id="getByName" resultMap="BaseMap" useCache="true">
与Redis集成
- 导入依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
- 指定二级缓存的具体实现,且开启二级缓存
<cache type="org.mybatis.caches.redis.RedisCache"/>
<select id="getByName" resultMap="BaseMap" useCache="true">
select * from student where name like concat('%',#{name},'%')
</select>
- mybatis-redis 中,Redis的连接不是通过yml,而是新建redis.properties文件进行设置
@Test
public void test01(){
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List<Student> list = mapper.getByName("核弹");
for (Student student : list) {
System.out.println(student);
}
System.out.println("same session---------");
List<Student> list2 = mapper.getByName("核弹");
for (Student student : list2) {
System.out.println(student);
}
System.out.println("same namespace-----------");
sqlSession.commit();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
StudentMapper mapper3 = sqlSession3.getMapper(StudentMapper.class);
List<Student> list3 = mapper3.getByName("核弹");
for (Student student : list3) {
System.out.println(student);
}
}
首次查询第二次查询原因是数据已经被写入Redis,缓存中有,则直接返回.
注意
- 被缓存的对象需要实现Serializable接口,否则开启二级缓存会报序列化错误。因为二级缓存我们是可以存到内存外的地方,必然会涉及序列化问题。
- SQLSession需要关闭或提交后,新的SQLSession才能读到二级缓存
在未提交前,SQLSession中的二级缓存都是暂存在transactionCacheManager中,只有提交后调用PerpetualCache.putObject放入map中,才能被其他SQLSession读取
useCache 和 flushCache的说明
- useCache只与二级缓存开启有关
- flushCache清除缓存,不分一级还是二级
例如:<select id = "xx" flushCache = "true"></select>
表示只有一级缓存,每次查询结束都会清空缓存,因为只有一级缓存,所以被清空的只有一级缓存<select id = "xx" useCache = "true" flushCache = "true"></select>
表示开启二级缓存,一级缓存默认开启,每次查询结束清空缓存,此时存在的一级和二级缓存都会被清空
// <select id="queryByNameLevel2Cache" useCache="true" flushCache="true">
public void enableLevel2Cache(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
List<UserLogin> list = mapper.queryByNameLevel2Cache("核弹");
for (UserLogin e : list) {
System.out.println(e);
}
System.out.println("\n========相同的SQLSession=======\n");
List<UserLogin> oneMore = mapper.queryByNameLevel2Cache("核弹");
for (UserLogin e : oneMore) {
System.out.println(e);
}
// session如果不关闭,二级缓存无法启用
sqlSession.close();
System.out.println("\n========新的SQLSession=======\n");
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserLoginMapper mapper2 = sqlSession2.getMapper(UserLoginMapper.class);
List<UserLogin> list2 = mapper2.queryByNameLevel2Cache("核弹");
for (UserLogin e : list2) {
System.out.println(e);
}
}public void enableLevel2Cache(){
SqlSession sqlSession = sqlSessionFactory.openSession();
UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
List<UserLogin> list = mapper.queryByNameLevel2Cache("核弹");
for (UserLogin e : list) {
System.out.println(e);
}
System.out.println("\n========相同的SQLSession=======\n");
List<UserLogin> oneMore = mapper.queryByNameLevel2Cache("核弹");
for (UserLogin e : oneMore) {
System.out.println(e);
}
// session如果不关闭,二级缓存无法启用
sqlSession.close();
System.out.println("\n========新的SQLSession=======\n");
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserLoginMapper mapper2 = sqlSession2.getMapper(UserLoginMapper.class);
List<UserLogin> list2 = mapper2.queryByNameLevel2Cache("核弹");
for (UserLogin e : list2) {
System.out.println(e);
}
}
三次都是查询数据库
测试代码