mybatis缓存bug
线上定时任务,在执行时,计算出合同金额不对;
查看日志
2.13 cron
2020-02-13 01:10:00.004 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-6] [com.tangdi.jump.bp.service.sqlmap.support.SqlSessionUtils] DEBUG Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@12af9e28] from current transaction
2020-02-13 01:10:00.004 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-6] [com.tangdi.jump.bp.service.sqlmap.impl.SqlMapImpl] DEBUG Opened SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@12af9e28] for iBATIS operation
2020-02-13 01:10:00.004 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-6] [com.tangdi.jump.bp.service.sqlmap.support.SqlSessionUtils] DEBUG Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@12af9e28]
2020-02-13 01:10:00.004 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-6] [net.tangdi.gxseas.service.AutoRecon] INFO === 日切日期: 2020-02-07
2020-02-13 01:10:00.004 [org.springframework.scheduling.quartz.SchedulerFactoryBean#0_Worker-6] [net.tangdi.gxseas.service.AutoRecon] INFO 任务号:A,任务执行步骤:A01,日切日期:20200207
定时任务是执行了,切定时记录时间正确是4月13日,但是应该执行4.12日的,为什么执行了4.07日的
一筹莫展后翻看了mybatis的源码,找到BaseExecutor,代码如下
// Flush the internal cache is force is trueif(ms.isFlushCacheRequired()) {clearLocalCache(); }
List list;
try{ queryStack++;
CacheKey key = createCacheKey(ms,parameter,rowBounds);
list= resultHandler == null ? (List) localCache.getObject(key): null;
if(list!= null) { handleLocallyCachedOutputParameters(ms,key,parameter); }
else{list= queryFromDatabase(ms,parameter,rowBounds,resultHandler,key); } }
finally { queryStack--; }
if(queryStack == 0) {for(DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } }
returnlist;}
resultHandler传入的默认全是null,说明它会缓存拿.最后在日志找到几天的sqlSession都是一样的,证实了缓存猜测;localCache是SqlSession的成员变量.那么就可以断定如果相同的sql在同一个SqlSession执行,就会命中缓存。
解决办法:sql单个查询取消默认缓存
<select id="queryTrastatusDateMax" resultType="String" flushCache="true">
<![CDATA[ SELECT TO_CHAR(MAX(DT),'YYYY-MM-DD') FROM WORK_HOLIDAY WHERE TRASTATUS = '1' ]]>
</select>
mybatis->configuration.xml->setting标签添加:
<setting name="localCacheScope" value="STATEMENT" />
或者
用插件,添加全局拦截
<plugins>
<plugin interceptor="com.xxxxx.interceptor.FlushCacheInterceptor" />
</plugins>
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.util.Properties;
/**
* 关掉Mybatis所有查询的一级缓存
* Created by mumubin 2017/4/13.
*/
@Intercepts(@Signature(method = "query", type = Executor.class, args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class}))
public class FlushCacheInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
MappedStatement copy = copyMappedStatementFlushCache(mappedStatement);
invocation.getArgs()[0] = copy;
return invocation.proceed();
}
/**
* 复制MappedStatement 并设置FlushCache=true
* @param mappedStatement
* @return
*/
private MappedStatement copyMappedStatementFlushCache(MappedStatement mappedStatement) {
MappedStatement.Builder builder = new MappedStatement.Builder(mappedStatement.getConfiguration(),
mappedStatement.getId(), mappedStatement.getSqlSource(), mappedStatement.getSqlCommandType());
builder.resource(mappedStatement.getResource());
builder.fetchSize(mappedStatement.getFetchSize());
builder.statementType(mappedStatement.getStatementType());
builder.keyGenerator(mappedStatement.getKeyGenerator());
builder.keyProperty(mappedStatement.getKeyProperty());
builder.timeout(mappedStatement.getTimeout());
builder.resultMaps(mappedStatement.getResultMaps());
builder.resultSetType(mappedStatement.getResultSetType());
builder.cache(mappedStatement.getCache());
builder.useCache(mappedStatement.isUseCache());
builder.parameterMap(mappedStatement.getParameterMap());
builder.flushCacheRequired(true);
return builder.build();
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {
}
}