背景
在单元测试中,我们需要验证不同的场景下系统功能是否正常。而构造不同的测试场景,就需要对db中的数据做相应的订正,同时通过事务的回滚,保证单元测试结束后,db中的数据回归到初始状态
问题描述
单元测试类上已经申明了@Transactional注解,在下面的这个测试场景代码中
result1 = queryResult();
jdbcTemplate.update("***");
jdbcTemplate.update("***");
result2 = queryResult();
result1和result2的结果完全一致,不符合预期
原因探讨
mybatis默认开启了localCache功能,默认的scope是session级别。那么在同一个session中,相同的Query重复执行的时候,直接从缓存中读取结果。
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
……
List<E> list;
try {
queryStack++;
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);
}
} finally {
queryStack--;
}
……
return list;
}
解决方案
只需要在select element中加上flushCache=”true”,则这个statement执行结束后,会将localCache清空,下一次查询的时候就会直接查询数据库
<select id="selectAll" resultMap="BaseResultMap" flushCache="true">
select
<include refid="Base_Column_List"/>
from Table
</select>
如果配置了这个选项,则在执行之前会将缓存清空,保证从数据库重新查询一次
@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);
……
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
不过需要注意的是,mybatis默认的作用域是session级别,也就是说清空以后,会影响这个session内的所有sql。
可以在mybatis的配置中,配置cache的作用域是session级别还是sql级别
<setting name="localCacheScope" value="SESSION"/>
<setting name="localCacheScope" value="STATEMENT"/>