严选供应商系统需要对供应商数据库进行加密,插入数据时加密插入,select后解密。发现有一个Dao操作查询出来的数据已经是被解密的导致异常。
怀疑是某个缓存导致。因此先验证:
因此基本可以判断是缓存的问题。
通过跟踪selectByExample的代码,发现在BaseExecutor的query方法里有如下代码:
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
即它会优先从localCache里查找,找不到则查询数据库。localCache结构如下:
Map<Object, Object> cache
再看查询数据库的实现:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
这里查询结果被放到了localCache里。
那么问题来了,为什么在测试过程中连续2次select没有命中这个缓存。 来看一下缓存被清空的时机:
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
下个断点,发现在 sqlSession.commit(true); 时被调到。也就是说,当两次select在同一个事务里时,缓存将会命中。
通过对测试代码加@Transactional注解,复现了这个问题,通过debug观察两次返回的对象其实是同一个对象,这也是由于Java语言的特性。
最后又顺便验证了delete或update操作是会清空缓存的。
因此,要防止以下这种代码:
@Transactional
fooBug() {
op();
...
op();
}
op() {
Foo f = dao.selectFoo(id);
if (f.flag == 0) {
// 第二次将执行不到这里
f.flag = 1;
...
}
}
这种情况下,前一次op()操作将会对下一次产生影响。 由于被封装到了一个函数里,很难发现问题的原因。另外, 这个缓存似乎没有开关可以关掉。
本文来自网易实践者社区,经作者郁利涛授权发布。