您的位置:首页 > 其它

Mybatis的一级缓存失效、关闭一级缓存源码分析

2020-02-01 08:03 113 查看

Mybatis的一级缓存失效

在MyBatis和Spring整合中,存在MyBatis一级缓存失效的情况。

SqlSessionTemplate
是SqlSession的默认实现,在
SqlSessionTemplate
的构造器中,创建了sqlSession的代理

//成员变量
private final SqlSessionFactory sqlSessionFactory;

private final ExecutorType executorType;

private final SqlSession sqlSessionProxy;

private final PersistenceExceptionTranslator exceptionTranslator;

//构造器
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {

notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");

this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
//为SqlsessionTemplate的内部类,实现JDK动态代理的Invocation
new SqlSessionInterceptor());
}

所以在每次调用sqlSession的方法时,都会进入代理的方法中

private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
//每次都会关闭sqlsession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}

在finally块里面,每次执行完方法都会关闭SqlSession。继续进入关闭方法里面

public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
//加了@Transaction注解,一级缓存就不会失效
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
}
holder.released();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
}
session.close();
}
}

在这里,可以看到,如果是加了@Transaction注解的方法,就不会执行下面的session.close()。那么一级缓存就不会失效
这里只看失效的方法,继续close()方法;这里会走到DefaultSqsession的close方法中

public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
closeCursors();
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}

最终都执行到BaseExecutor#close()方法

public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore.  There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction.  Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}

可以看到localCache=null;这就是一级缓存失效的原因

关闭MyBatis一级缓存

Mybatis一级缓存导致重新查询第二次的数据和第一次查询的不一致,虽然很少会在一次session中查询两次,但比如在和银行支付交互,等待银行异步通知回调的时候,查询状态和确认状态时会调用同样的查询方法两次。所以这时候需要关闭一级缓存,其实一级缓存是无法关闭的,下面的方法只在每次查询时候删除之前的缓存:

  1. 在< select />语句中,加入
    flushCache="true"
    ,就可以在此次查询中刷新一级缓存
  2. 在< select />语句中,加入
    statementType="STATEMENT"
    ,就可以在此次查询中关闭一级缓存
  3. 使用拦截器拦截query方法,得到CachingExecutor,调用clearLoaclCache()方法关闭一级缓存,拦截器对所有方法都起作用.ps:Mybatis默认用CachingExecutor装饰SimpleExecutor,因为默认的cacheEnable属性为true
  4. 获取到当前sqlsession,调用clearCache方法,这个方法最终调用BaseExecutor的clearLocalCache()方法。

下面看具体是怎么实现的:
从拦截器让入手,因为是拦截query方法,所以看开启了缓存的

CachingExecutor#query
方法

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);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

从这段代码可以看到,

ms.isUseCache()
是判断是否使用二级缓存(在< select />标签中加入
useCache="true"
),也由此看出MyBatis是优先使用二级缓存的
继续往下走,进入到
BaseExecutor#query()

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//如果调用栈为0,并且flushCache="true,就清空缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
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--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
//如果设置了 statementType="STATEMENT",清空缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}

可以看到,加入了

flushCache="true
或者
statementType="STATEMENT"
,会刷新缓存

public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}

这个方就是使用拦截器获得的Executor调用的clearLocalCache()方法。
到此,已经可以看到关闭一级缓存的原理了~~

  • 点赞
  • 收藏
  • 分享
  • 文章举报
微尘_hck 发布了4 篇原创文章 · 获赞 2 · 访问量 504 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: