SqlSession原理中介绍了在SQL查询时一级缓存和二级缓存的调用过程。这里介绍一下缓存的场景和失效。
一、一级缓存 一级缓存与SqlSession
相关,是一个会话级别的缓存 ,会话关闭清空缓存。在会话中一级缓存与运行时参数和操作配置相关
命中场景 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class FirstCacheTest { private Configuration configuration; private SqlSession sqlSession; @Before public void init () throws IOException, SQLException { SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder(); InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml" ); SqlSessionFactory build = factoryBuilder.build(inputStream); configuration = build.getConfiguration(); sqlSession = build.openSession(); } @Test public void test () { AccountMapper mapper = sqlSession.getMapper(AccountMapper.class); List<Account> accounts1 = mapper.selectById(1 ); List<Account> accounts2 = mapper.selectById(1 ); System.out.println(accounts1 == accounts2); } }
缓存命中有几个运行时参数要求
sql和参数必须相同
必须是相同的statementID
,sql相同也不行
sqlSession
必须相同
RowBounds
返回行返回必须相同
清理缓存之后就无法命中 ,默认采用的是simple执行器,编译2次,执行2次。建议采用的reuse执行器。只有有一条执行语句加入了flushCache=true
等同于 sqlSession.clearCache()
。共有4种操作配置导致缓存无法命中的场景:
手动sqlSession.clearCache()
,sqlSession.commit()
, sqlSession.rollback()
执行的方法上加入flushCache=true
中间执行Update语句
缓存作用域不是SESSION
改为STATEMENT
这里是嵌套查询(子查询)
1 2 3 4 5 6 7 8 @Test public void test2 () { AccountMapper mapper = sqlSession.getMapper(AccountMapper.class); List<Account> accounts1 = mapper.selectById(1 ); sqlSession.clearCache(); List<Account> accounts2 = mapper.selectById(1 ); System.out.println(accounts1 == accounts2); }
一级缓存源码解析 sqlSession
查询时的流程:
命中缓存 其中的BaseExecutor
中的localCache
的流程:
BaseExecutor
中的query源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 @SuppressWarnings("unchecked") 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." ); 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(); } deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); } } return list; } 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; }
一级缓存的key,只有都相同才能命中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class CacheKey implements Cloneable , Serializable { private static final long serialVersionUID = 1146682552656046210L ; public static final CacheKey NULL_CACHE_KEY = new NullCacheKey(); private static final int DEFAULT_MULTIPLYER = 37 ; private static final int DEFAULT_HASHCODE = 17 ; private int multiplier; private int hashcode; private long checksum; private int count; private List<Object> updateList; }
这5个参数分别为,statementID、分页上下限、sql语句、参数
清空缓存 清空缓存的操作
1 2 3 4 5 6 public void clearLocalCache () { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } }
一共有4个地方调用了这个方法query、update、commit、rollback
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public <E> List<E> query (MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } ... if (queryStack == 0 ) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); } } } 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); } public void commit (boolean required) throws SQLException { if (closed) throw new ExecutorException("Cannot commit, transaction is already closed" ); clearLocalCache(); flushStatements(); if (required) { transaction.commit(); } } public void rollback (boolean required) throws SQLException { if (!closed) { try { clearLocalCache(); flushStatements(true ); } finally { if (required) { transaction.rollback(); } } } }
Spring一级缓存失效 会话不相同,如果不配置事务,每次都会新建一个session
1 2 3 4 5 6 7 public void testBySpring () { ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring.xml" ); AccountMapper mapper = context.getBean(AccountMapper.class); List<Account> accounts1 = mapper.selectById(1 ); List<Account> accounts2 = mapper.selectById(1 ); System.out.println(accounts1 == accounts2); }
这里的UserMapper
与从SqlSession
取出的UserMapper
不同,每次构造都会构造一个新会话,两次查询都是一个新会话。SqlSession
和Executor
是一对一的关系。每次查询的Executor
不同。
开启事务,后就会有效
1 2 3 4 5 6 7 8 9 10 11 public void testBySpring () { ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("spring.xml" ); AccountMapper mapper = context.getBean(AccountMapper.class); DataSourceTransactionManager transactionManager =(DataSourceTransactionManager) context.getBean("txManager" ); TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); List<Account> accounts1 = mapper.selectById(1 ); List<Account> accounts2 = mapper.selectById(1 ); System.out.println(accounts1 == accounts2); }
mapper -> SqlSessionTemplate
-> SqlSessionInterceptor
-> SqlSessionFactory
动态代理嵌入动态代理最终调用Mybatis
的 SqlSessionFactory
这里的sqlSession
是SqlSessionTemplate
,sqlSessionProxy
就是SqlSessionInterceptor
SqlSessionTemplate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class SqlSessionTemplate implements 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 }, new SqlSessionInterceptor()); } }
通过2层的动态代理,实现Spring的事务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private class SqlSessionInterceptor implements InvocationHandler { 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)) { sqlSession.commit(true ); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this .exceptionTranslator != null && unwrapped instanceof PersistenceException) { 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 ) { closeSqlSession(sqlSession, SqlSessionTemplate.this .sqlSessionFactory); } } } }
获取会话
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public static SqlSession getSqlSession (SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, "No SqlSessionFactory specified" ); notNull(executorType, "No ExecutorType specified" ); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if (holder != null && holder.isSynchronizedWithTransaction()) { if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction" ); } holder.requested(); if (logger.isDebugEnabled()) { logger.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction" ); } return holder.getSqlSession(); } if (logger.isDebugEnabled()) { logger.debug("Creating a new SqlSession" ); } SqlSession session = sessionFactory.openSession(executorType); ... }
将断点打在holder,通过堆栈查看
1 2 3 4 5 6 7 8 9 10 11 12 13 public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } public <E> List<E> selectList (String statement, Object parameter) { return this .sqlSessionProxy.<E> selectList(statement, parameter); }
invoke的动态代理是Mybatis
的动态代理,这里的sqlSession
是SqlSessionTemplate
,原本的SqlSession
是DefaultSqlSession
。发起会话的调用是会调用到SqlSessionTemplate
的逻辑。但是SqlSessionTemplate
没有能力发起对Mybatis
代码的代用,所以最终还是会调用DefaultSqlSession
中的逻辑,而SqlSessionTemplate
中的每个方法都会打开会话构造SqlSession
,所以将这些方法中构造DefaultSqlSession
的逻辑使用动态代理来实现。
而getSqlSession
方法获取SqlSession
,不使用事务时,每次从获取一个SqlSession
都是null,都会使用工厂方法创建一个SqlSession
,然后注册到事务上,当方法中不使用事务时,就会注册失败。 每次的SqlSession都是新构建的,所以无法命中一级缓存。
二、二级缓存 定义和需求
Mybatis
中是先走会话,再走二级缓存,再走一级缓存。
二级缓存定义: 二级缓存也称作是应用级缓存,与一级缓存不同的是它的作用范围是整个应用 ,而宜可以跨线程使用 。所以二级缓存有更高的命中率,适合缓存一些修改较少的数据 。
二级缓存扩展性要求 因为二级缓存是应用级别的缓存,必然对缓存有所要求。
存储:需要多种方式的缓存,分为内存、硬盘、第三方集成。
缓存策略:当缓存到容量上限时,就需要有一些策略淘汰其中的缓存。FIFO先进先出、LRU最近最少使用。
过期清理:缓存设置过期清理。
线程安全:多线程场景下,必须保证数据一致。
命中率统计:对缓存中数据进行命中统计。
序列化:跨线程使用,2个对象必须不一样。
…
二级缓存组件结构 MyBatis的缓存接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface Cache { String getId () ; int getSize () ; void putObject (Object key, Object value) ; Object getObject (Object key) ; Object removeObject (Object key) ; void clear () ; ReadWriteLock getReadWriteLock () ; }
Mybait的二级缓存采用了装饰器加责任链的设计模式。每一层都实现自己的功能,并通过代理的方式实现Cache的所有功能。
开启mybatis二级缓存
配置
解释
cacheEnabled
全局缓存开关默认true
useCache
statement 缓存开关默认true
flushCache
清除默认:修改true、查询false
或 @CacheNamespace
声明缓存空间
或 @CacheNamespaceRef
引用缓存空间
配置文件中加入
1 2 3 <settings > <setting name ="cacheEnabled" value ="true" /> </settings >
mapper文件中加入
1 2 3 4 5 <cache eviction ="FIFO" flushInterval ="60000" size ="512" readOnly ="true" />
就开启了二级缓存
对应的sql语句加上useCache,flushCache就会开启响应策略
和@CacheNamespace
不是一个命名空间
测试二级缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public void cacheTest1 () { Cache cache = configuration.getCache("com.lq.mybatis.mapper.AccountMapper" ); Account account = new Account(); account.setId(1 ); account.setMoney(1000.0 ); account.setName("test" ); cache.putObject("lq" , account); cache.getObject("lq" ); } } @Test public void cacheTest5 () { SqlSession session1 = factory.openSession(true ); AccountMapper mapper1 = session1.getMapper(AccountMapper.class); mapper1.selectById(1 ); session1.commit(); SqlSession session2 = factory.openSession(true ); AccountMapper mapper2 = session2.getMapper(AccountMapper.class); mapper2.selectById(1 ); }
这样设计Cache就可以屏蔽内部复杂性
二级缓存的命中条件
会话提交之后
Sql语句、参数相同
相同的statementID
RowBounds相同
二级缓存源码分析 二级缓存命中 为什么要提交后才能命中缓存?
如果不提交就命中会产生脏数据。
会话中访问缓存空间
每次执行mapper的方法时都会把代码提交到暂存区,只有当commit时才会提交到缓冲区。
这里的缓冲区就是SynchronizedCache
二级缓存执行流程
CachingExecutor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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); } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
TransactionalCacheManager缓存管理器,管理暂存区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class TransactionalCacheManager { private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>(); public void clear (Cache cache) { getTransactionalCache(cache).clear(); } public Object getObject (Cache cache, CacheKey key) { return getTransactionalCache(cache).getObject(key); } public void putObject (Cache cache, CacheKey key, Object value) { getTransactionalCache(cache).putObject(key, value); } public void commit () { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } public void rollback () { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.rollback(); } } private TransactionalCache getTransactionalCache (Cache cache) { TransactionalCache txCache = transactionalCaches.get(cache); if (txCache == null ) { txCache = new TransactionalCache(cache); transactionalCaches.put(cache, txCache); } return txCache; } }
TransactionalCache暂存区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class TransactionalCache implements Cache { @Override public Object getObject (Object key) { Object object = delegate.getObject(key); if (object == null ) { entriesMissedInCache.add(key); } if (clearOnCommit) { return null ; } else { return object; } } public void clear () { clearOnCommit = true ; entriesToAddOnCommit.clear(); } public void commit () { if (clearOnCommit) { delegate.clear(); } flushPendingEntries(); reset(); } }