myBatis使用redis做二级缓存时需要注意的几个地方

myBatis使用redis做二级缓存时需要注意的几个地方

最新在做的一个项目,由于读业务多,写业务不频繁,所以采用redis做了个二级缓存来提高读的效率,但是在整个过程中,遇到了一些比较容易犯,而且也很严重的错误。

环境:spring boot redis

redis缓存突然全部丢失

由于原来redis缓存只应用在myBatis的缓存上,所以并没有即时发现这个问题,后来由于业务需要,额外在redis中主动缓存了一些需要处理一段时间才能计算出来的数据,才发现这个redis缓存在某些条件触发下会全部清空的问题。

具体导致这个问题的是由于实现org.apache.ibatis.cache.Cache的配置类中的clear方法不合适导致的。

在网上查到的很多Spring Boot + Mybatis + Redis做二级缓存的资料都是这样配置的:

上面这种写法,直接调用connection.flushDb();会删除当前选定数据库的所有键,这也就导致很多不该被删除的缓存数据,都被删除了。很多像我一样的小白并没有深究这种写法,照抄下来,结果就会很悲剧了,而且发生之后还一脸懵逼,不知道哪儿出了问题。

那应该如何解决这个问题呢?我目前采用的是以下方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
String id = this.id;
/*
* 这里不论是调用keys方法还是调用del方法,都只能使用connection,不能使用redisTemplate,否则无效!!!!!!!!!!!!!
*
* 参考资料:https://stackoverflow.com/questions/19098079/how-to-get-all-keys-from-redis-using-redis-template
*/
redisTemplate.execute((RedisCallback) connection -> {
String patten = "*:" + id + "*";
Set<byte[]> keys = connection.keys(patten.getBytes());
for (byte[] data : keys) {
connection.del(data);
}
connection.close();
return null;
});
logger.warn("Clear cached query result from redis");
}

这种方式就只会去删除与缓存实例ID相匹配的键的缓存数据。完整的配置类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class MyBatisRedisCache implements Cache {
private static Logger logger = LoggerFactory.getLogger(MyBatisRedisCache.class);

private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final String id; // cache instance id
private RedisTemplate redisTemplate;
private static final long EXPIRE_TIME_IN_MINUTES = 72 * 60 * 60; // redis过期时间

public MyBatisRedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
this.id = id;
}

@Override
public String getId() {
return id;
}

@Override
@SuppressWarnings("unchecked")
public void putObject(Object key, Object value) {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
// 键值要toString
opsForValue.set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
logger.debug("Put query result to redis");
}

@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
logger.debug("Get cached query result from redis");
return opsForValue.get(key.toString());
}

@Override
@SuppressWarnings("unchecked")
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(key.toString());
logger.debug("Remove cached query result from redis");
return null;
}

@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
String id = this.id;
/*
* 这里不论是调用keys方法还是调用del方法,都只能使用connection,不能使用redisTemplate,否则无效!!!!!!!!!!!!!
*
* 参考资料:https://stackoverflow.com/questions/19098079/how-to-get-all-keys-from-redis-using-redis-template
*/
redisTemplate.execute((RedisCallback) connection -> {
String patten = "*:" + id + "*";
Set<byte[]> keys = connection.keys(patten.getBytes());
for (byte[] data : keys) {
connection.del(data);
}
connection.close();
return null;
});
logger.warn("Clear cached query result from redis");
}

@Override
public int getSize() {
Long size = (Long) redisTemplate.execute((RedisCallback<Long>) connection -> connection.dbSize());
logger.info("cached count:" + size.intValue());
return size.intValue();
}

@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}

private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
redisTemplate = (RedisTemplate) SpringContextUtil.getApplicationContext().getBean("redisTemplate");
}
return redisTemplate;
}
}