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中查询两次,但比如在和银行支付交互,等待银行异步通知回调的时候,查询状态和确认状态时会调用同样的查询方法两次。所以这时候需要关闭一级缓存,其实一级缓存是无法关闭的,下面的方法只在每次查询时候删除之前的缓存:
- 在< select />语句中,加入
flushCache="true"
,就可以在此次查询中刷新一级缓存 - 在< select />语句中,加入
statementType="STATEMENT"
,就可以在此次查询中关闭一级缓存 - 使用拦截器拦截query方法,得到CachingExecutor,调用clearLoaclCache()方法关闭一级缓存,拦截器对所有方法都起作用.ps:Mybatis默认用CachingExecutor装饰SimpleExecutor,因为默认的cacheEnable属性为true
- 获取到当前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()方法。
到此,已经可以看到关闭一级缓存的原理了~~
- 点赞
- 收藏
- 分享
- 文章举报
相关文章推荐
- Mybatis源码分析之Cache一级缓存原理(四)
- MyBatis框架及源码分析(3)—— MyBatis的一级缓存及二级缓存
- mybatis源码分析(5)-----拦截器的实现原理(动态代理+责任链)
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- Android系统原理与源码分析(1):利用Java反射技术阻止通过按钮关闭对话框
- 通过源码分析MyBatis的缓存
- Mybatis源码分析(一)- Configuration配置文件详解
- Mybatis3源码分析(02)-加载Configuration-XMLConfigBuilder
- Mybatis源码分析(二)- SqlSessionFactory和SqlSession详解
- MyBatis源码分析
- MyBatis 源码分析 - 缓存原理
- MyBatis-3.4.2-源码分析4:解析XML之pluginElement(root.evalNode("plugins"))
- mybatis源码分析之TypeHandler
- Mybatis实现【4】-查询解析(一次SQL查询的源码分析)
- Spring源码分析【8】-MyBatis注解方法不能重载
- 【Mybatis源码分析】Mybatis源码分析-数据库连接池
- mybatis源码解析 - 通过一个简单查询例子分析流程
- MyBatis源码分析-SQL语句执行的完整流程
- Mybatis3源码分析(21)-Mapper实现-动态代理
- mybatis 源码分析(八)ResultSetHandler 详解