mybatis缓存使用
缓存的定义
缓存一般是orm框架会提供的功能,目的是提升查询效率和减少数据库的压力。Mybatis有1级缓存和2级缓存,并且还有集成第三方的缓存工具比如说 redis。
一级缓存
一级缓存是默认开启的不需要配置。同时因为它是sqlSession级别的也叫会话缓存。在同一个会话里面,多次执行相同的 SQL 语句,会直接从内存取到缓存的结果,不会再发送 SQL 到数据库。但是不同的会话里面,即使执行的 SQL 一模一样(通过一个 Mapper 的同一个方法的相同参数调用),也不能使用到一级缓存。
命中缓存
@Test
public void selectListCacheOne() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
List<User> userList1 = userMapper1.selectList();
UserMapper userMapper2 = sqlSession1.getMapper(UserMapper.class);
List<User> userList2 = userMapper2.selectList();
}
返回结果是:
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5ccddd20]
==> Preparing: select * from user
==> Parameters:
interface java.util.List
上面的代码同一个sqlSession中执行2次查询 只打印出了一次sql语句。可见第2次查询命中了缓存。
未命中缓存
@Test
public void selectListNoneCacheOne() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
List<User> userList1 = userMapper1.selectList();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
List<User> userList2 = userMapper2.selectList();
}
返回结果
==> Preparing: select * from user
==> Parameters:
interface java.util.List
==> Preparing: select * from user
==> Parameters:
interface java.util.List
2个sqlsession执行了2次查询而sql语句打印了2次。由此可见不同不同的sqlsession查询同一个sql不会命中缓存。
一级缓存的弊端
我们知道一级缓存是sqlsession级别的。那么我在方法1中用sqslsession查询语句

同时打上断点。在这个时候执行另一个线程插入一条数据
@Test
public void mybatisMapperInsert() {
//4 获取sqlSession SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
SqlSession sqlSession = sqlSessionFactory.openSession();
//5 获取sql 通过代理模式 代理了一个映射器
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User userParam = new User();
userParam.setAge(1);
userParam.setSexState(SexEnum.MAN);
userParam.setSexStateOrigin(SexEnumOrigin.MAN);
userMapper.insert(userParam);
sqlSession.commit();
}
然后这个时候再次执行方法1中的线程。

可以看到在同一个sqlsession中命中了缓存,第2次查询结果是还是13个。但是数据库中明明是14条数据。所以可见多线程的情况下 一级缓存会出现脏读的情况。 那么怎么避免呢?用2级缓存
二级缓存
二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是 namespace 级别 的,可以被多个 SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享), 生命周期和应用同步。作为一个作用范围更广的缓存,它肯定是在 SqlSession 的外层,否则不可能被多个 SqlSession 共享。而一级缓存是在 SqlSession 内部的,也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存。

开启二级缓存
在想要开启二级缓存的Mapper中添加表桥<cache/>
<mapper namespace="com.mybatis.demo.mybatis.mapper.UserMapper">
<cache eviction="LRU" flushInterval="600000" size="1024" readOnly="true"/>
属性 | 含义 | 取值 | |
---|---|---|---|
type | 2级缓存的实现类 | 默认是需要实现 Cache 接口,默认是 PerpetualCache | |
size | 最多缓存对象数量 | 默认是1024 | |
eviction | 回收策略 | LRU(默认) FIFO SOFT WEAK | |
flushInterval | 定时自动清理缓存 | 自动刷新时间,单位 ms,未配置时只有调用时刷新 | |
readOnly | 是否只读 | false 默认 crud操作会更新缓存 true:只读缓存 性能提高 | |
blocking | 是否使用可重入锁实现 缓存的并发控制 | true,会使用 BlockingCache 对 Cache 进行装饰 默认 false | |
Mapper.xml 配置了<cache>之后,select()会被缓存。update()、delete()、insert() 会刷新缓存。
如果某些查询方法对数据的实时性要求很高,不需要二级缓存,怎么办? 我们可以在单个 Statement ID 上显式关闭二级缓存(默认是 true):
<select id="selectList" resultMap="BaseResultMap" useCache="false">
select * from user
</select>
这样子在命名空间com.mybatis.demo.mybatis.mapper.UserMapper就开启了一个缓存
例子
@Test
public void selectListCacheTwoUpdate() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
List<User> userList1 = userMapper1.selectList();
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
List<User> userList2= userMapper2.selectList();
sqlSession2.close();
}
返回结果
Cache Hit Ratio [com.mybatis.demo.mybatis.mapper.UserMapper]: 0.5
打印出这个信息代表了命中缓存。
集成redis缓存
mybatis提供了集成redis的缓存 引入maven
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
mapper中添加缓存的type
<cache type="org.mybatis.caches.redis.RedisCache" flushInterval="60000" size="512" />
执行的效果与上面的一样
spring+redis+mybatis缓存
mybatis虽然提供了redis缓存但是没有提供对spring的支持。所以想要在spring缓存下使用需要自己实现对myabtis缓存的支持需要集成接口 org.apache.ibatis.cache.Cache
@Component
public class MybatisRedisCache implements Cache {
/**
* ID
*/
private String id;
/**
* 集成redisTemplate
*/
private static RedisTemplate redisTemplate;
public MybatisRedisCache() {
}
public MybatisRedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
} else {
this.id = id;
}
}
@Override
public String getId() {
return this.id;
}
@Override
public int getSize() {
try {
Long size = redisTemplate.opsForHash().size(this.id.toString());
return size.intValue();
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}
@Override
public void putObject(final Object key, final Object value) {
try {
redisTemplate.opsForHash().put(this.id.toString(), key.toString(), value);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object getObject(final Object key) {
try {
Object hashVal = redisTemplate.opsForHash().get(this.id.toString(), key.toString());
return hashVal;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public Object removeObject(final Object key) {
try {
redisTemplate.opsForHash().delete(this.id.toString(), key.toString());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@Override
public void clear() {
try {
redisTemplate.delete(this.id.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "MybatisRedisCache {" + this.id + "}";
}
public static void setRedisTemplate(RedisTemplate redisTemplate) {
MybatisRedisCache.redisTemplate = redisTemplate;
}
}
private static RedisTemplate redisTemplate 为了集成spring redis,实现的缓存类中需要静态注入redisTemplate。通过静态方法
public class RedisCacheTransfer {
@Autowired
public void serRedisTemplate(RedisTemplate redisTemplate) {
MybatisRedisCache.setRedisTemplate(redisTemplate);
}
@Bean
public RedisCacheTransfer redisCacheTransfer(RedisTemplate redisTemplate) {
RedisCacheTransfer redisCacheTransfer = new RedisCacheTransfer();
redisCacheTransfer.serRedisTemplate(redisTemplate);
return redisCacheTransfer;
}
结果

cache 有多个实例,采用的装饰器模式
2级缓存 要等session关闭才会放入到缓存中 这是为什么