二级缓存的使用
mybatis 二级缓存不默认开启,需要手动配置,因为二级缓存是mapper级别的,所以不同的mapper可以使用不同的缓存策略。
二级缓存开启需要三个步骤
-
config.xml中需要配置,这个配置默认为true所以可以省略:
上述配置对应的源码解析和使用:
像核心配置文件中的配置肯定是在mybatis第一步 *** 作,只有解析完配置文件才能后续的 *** 作,而且mybatis解析完都会把配置存到Configuration对象中,这里对缓存配置的代码如下图: XMLConfigbuilder:
根据代码显示默认配置就是true,所以这里的配置是可以省略的。
那在哪里用到了这个配置呢?
SqlSessionFactory在的openSession时候,会实例化一个Executor对象,如下图:
在上图代码中当cacheEnabled为true,就会实例化一个CachingExecutor对象(这里用到了装饰器的设计模式,对于设计模式这里不做赘述了),这个CachingExecutor就是对缓存使用的执行器。
-
Mapper.xml中:
#默认使用mybatis提供的二级缓存实现
这时候在创建SqlSession的时候就把Excuotr创建好了, 代码在SqlSessionFactory的openSession中:
@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //这里调用newExecutor方法,拿到Executor对象。 final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } //该方法返回Execuotr对象。 public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } //这里有一个判断,如果cacheEnabled为true,则返回一个装饰类CachingExecutor, //所以核心在这里,为什么SqlSession中的Executor接口时CachingExecutor类对象了,而不是SimpleExecutor对象,除非在配置文件中,将cacheEnabled的配置设置为false。 if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
因此我们继续看CachingExecutor类中的query方法:
@Override public
List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //获取BoundSql对象 BoundSql boundSql = ms.getBoundSql(parameterObject); //创建Cachekey对象,即缓存中的key值。 CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); //调用重载的方法。 return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } @Override public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { //从MappedStatement对象ms中获取二级缓存cache。 Cache cache = ms.getCache(); //如果缓存不为空 if (cache != null) { //判断是否配置flushCache标签,是否在查询前刷新缓存。 flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, parameterObject, boundSql); //先从二级缓存中获取。 @SuppressWarnings("unchecked") List list = (List ) tcm.getObject(cache, key); if (list == null) { //获取不到就调用SimpleExecutor的query方法,即再去一级缓存中去取,拿不到在查询数据库。 list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); //查询成功之后,在缓存到二级缓存中。 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } 这里有一个tcm变量:
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
TransactionalCacheManager 这个类是一个缓存事务管理器,在上面的代码中,调用了其getObject和putObject方法。分别来看下这两个方法.
getObject()方法:
//定义了一个map, //key值时 Cache对象,就是在解析
标签时候创建的Cache对象, //value值是一个TransactionalCache对象。 private final Map transactionalCaches = new HashMap (); //该方法内部调用了getTransactionalCache 这个方法。 public Object getObject(Cache cache, CacheKey key) { //这里调用的是返回的TransactionalCache 对象的getObject()方法。 return getTransactionalCache(cache).getObject(key); } //该方法从transactionalCaches这个map中拿出 //TransactionalCache对象,如果没有则new一个新的放到map中。 private TransactionalCache getTransactionalCache(Cache cache) { TransactionalCache txCache = transactionalCaches.get(cache); if (txCache == null) { txCache = new TransactionalCache(cache); transactionalCaches.put(cache, txCache); } return txCache; } 上面分析到最后调用的是TransactionalCache对象的getObject方法。 TransactionalCache的getObject()方法:
@Override public Object getObject(Object key) { // issue #116 //这里调用的是delegate的getObject方法。 //也就是说mybatis二级缓存是从delegate的getObject方法中取出的。 //这个delegate是TransactionalCache内部的维护的一个Cache类型的一个 缓存成员变量,实际上TransactionalCache就是一个装饰类,因为mybatis机制用到了装饰器模式。 Object object = delegate.getObject(key); if (object == null) { entriesMissedInCache.add(key); } // issue #146 if (clearOnCommit) { return null; } else { return object; } }
以上就是总结分析了从二级缓存中数据的过程。 下面再来看看TransactionalCacheManager中的putObject()方法:
public void putObject(Cache cache, CacheKey key, Object value) { //这里同样的道理,与getObject()方法调用的一样的方法getTransactionalCache //所以说这里putObject调用的也就是TransactionalCache的putObject()方法了。 getTransactionalCache(cache).putObject(key, value); }
紧接着看一下TransactionalCache 的putObject方法:
从下面的代码看可以看出TransactionalCache中的putObject方法最终将数据存入到了一个map集合中。
private final Map
那么问题来了,二级缓存取数据是从delegate中取,存数据是存到TransactionalCache的entriesToAddOnCommit这个map中去,这明显是不对的。
那这是为什么呢?我们在 *** 作数据库的过程中,能够发现如果我们在进行一次数据库查询 *** 作之后,如果commit的话,会发现第二次同样的查询还是会发送数据库语句取查询数据库,这就是问题所在,因为mybatis为了防止脏读,会先将数据存放到map中,当事务提交或者session关闭会将数据提交到二级缓存delegate中去。
下面我们来看一下TransactionalCache 的commit方法:
//commit方法调用了flushPendingEntries这个方法。 public void commit() { if (clearOnCommit) { delegate.clear(); } flushPendingEntries(); reset(); } //该方法是将entriesToAddonCommit 这个map中暂缓的数据提交到delegate中。实现二级缓存的刷新。 private void flushPendingEntries() { //遍历entriesToAddOnCommit这map提交到delegate中。 for (Map.Entry
以上是个人对mybatis二级缓存的学习心得,如有模糊之处,忘不吝赐教!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)