最近由于业务的需要(系统对数据的操作用的是mybatis),需要对数据库中的某些字段进行加密。为了保证对外层应用的透明性,对SqlSessionTemplate 的部分方法进行了重写.一般对inser update 的时候,对插入的部分列加密,对select 的时候对加密的字段进行解密
如对update方法重写如下
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
// TODO Auto-generated method stub
return (List<E>) returnDeCryption(statement, super.selectList(statement, parameter, rowBounds));
}
开始用的时候都加密解密的时候很正常。但是有时候操作在做解密的时候,解密失败。
通过分析,是在一个功能中,对同一个select 方法调用两次的时候解密失败.通过分析,原来是mybatis 的缓存导致的。
具体分析如下:
mybatis中有一个缓存类
public class PerpetualCache implements Cache {
private String id;
//缓存key value 数据的map 对象
private Map<Object, Object> cache = new HashMap<Object, Object>();
真正使用缓存的对象在下边类中,
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
//缓存
protected PerpetualCache localCache;
//缓存
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
localCache 缓存查询的结果
<pre name="code" class="java">localOutputParameterCache 缓存执行的参数
//清楚缓存
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
//update 数据的时候会调用clearLocalCache方法清楚缓存,这样保证update 后,如果调用同一个select 查询到的是最新的结果
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) throw new ExecutorException("Executor was closed.");
clearLocalCache();
return doUpdate(ms, parameter);
}
//查询的时候,如果是同一条sql 并且是同样的参数,就会把查询结果以及查询语句放到缓存中
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;
}
通过查看BaseExecutor的源码,对导致问题的原因一目了然了。
1:mybatis 对同一个select (执行sql以及对应的sql参数的值都一样时才是同一次select),执行多次时,只有第一次去查询数据库,后续的查询是直接从缓存中把结果取出
2:由于重写了selectList方法,对每次查询的结果都进行解密,但是实际上第一次查询时就对结果进行了解密,此时mybatis 已经是缓存的明文数据,当第二查询的时候,查询到的已经是明文数据,所以解密出错。