2021-05-28 21:55  阅读(1566)
文章分类:Mybatis 源码分析 文章标签:MybatisMybatis 源码
©  原文作者:ashan_li 原文地址:https://blog.csdn.net/ashan_li/category_6047775.html

Mybatis主要有两种缓存:一级缓存和二级缓存。

一级缓存的生命周期与SqlSession的生命周期一样。一级缓存是在BaseExecutor中实现。

二级缓存的生命周期跟SqlSessionFactory一样,通常在整个应用中有效。二级缓存是通过CachingExecutor来实现的。

一级缓存

Mybatis提供了如下方式来配置一级缓存:

        <setting name="localCacheScope" value="SESSION|STATEMENT"/>
    

SESSION表示在整个SqlSession中有效。

STATEMENT表示在STATEMENT中有效?暂时理解为不使用一级缓存。

在BaseExecutor中会有一个localCache对象,就是来保存缓存数据的。

         protected BaseExecutor(Configuration configuration, Transaction transaction) {
            this.transaction = transaction;
            this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
            //创建一个缓存对象,PerpetualCache并不是线程安全的,但SqlSession和Executor对象在通常情况下只能有一个线程访问,而且访问完成之后马上销毁。
            this.localCache = new PerpetualCache("LocalCache");
    
            //这是执行过程中的缓存,这里不做分析。
            this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
            this.closed = false;
            this.configuration = configuration;
            this.wrapper = this;
          }
    

再来看BaseExecutor中的query方法是怎么实现一级缓存的

        public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
            BoundSql boundSql = ms.getBoundSql(parameter);
            //利用sql和执行的参数生成一个key,如果同一sql不同的执行参数的话,将会生成不同的key
            CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
            return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
         }
    
          @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 {
               //如果缓存中没有数据,将sql执行生成结果,并加入localCache中。
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
              }
            } finally {
              queryStack--;
            }
            if (queryStack == 0) {
              for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
              }
              deferredLoads.clear(); // issue #601
              if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                //如果配置为STATEMENT时,将清除所有缓存。说明STATEMENT类型的查询只有queryFromDatabase方法中有效。
                clearLocalCache(); // issue #482
              }
            }
            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 {
              //执行sql生成数据
              list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
            } finally {
              localCache.removeObject(key);
            }
            //将缓存加入到localCache中
            localCache.putObject(key, list);
            if (ms.getStatementType() == StatementType.CALLABLE) {
              localOutputParameterCache.putObject(key, parameter);
            }
            return list;
          }
    

如果执行了update方法,localCache也会被清除:

        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.");
            //每次执行update/insert/delete语句时都会清除一级缓存。
            clearLocalCache();
            return doUpdate(ms, parameter);
          }
    

以上代码可以看出一级缓存中的基本策略。

  1. 一级缓存只在同一个SqlSession中共享数据
  2. 在同一个SqlSession对象执行相同的sql并参数也要相同,缓存才有效。
  3. 如果在SqlSession中执行update/insert/detete语句的话,SqlSession中的executor对象会将一级缓存清空。

二级缓存

二级缓存对所有的SqlSession对象都有效。需要注意如下几点:

  1. 二级缓存是跟一个命名空间绑定的。
  2. 在一个SqlSession中可以执行多个不同命名空间中的sql,也是就说一个SqlSession需要对多个Cache进行操作。
  3. 调用SqlSession.commit()之后,缓存才会被加入到相应的Cache。

下面来看CachingExecutor是怎么实现的。

        private TransactionalCacheManager tcm = new TransactionalCacheManager();
    

这个manager实现了对多个Cache的管理,SqlSession.commit()之后,数据加入到相应的Cache也是由这个对象来实现的。

如下是CachingExecutor的commit()和rollback()方法

        public void commit(boolean required) throws SQLException {
            //提交数据库的事务
            delegate.commit(required);
            //将数据刷新到Cache中,使数据对其他的SqlSession也可见
            tcm.commit();
          }
    
          public void rollback(boolean required) throws SQLException {
            try {
              delegate.rollback(required);
            } finally {
              if (required) {
                //清除临时的数据,不将数据刷新到Cache中
                tcm.rollback();
              }
            }
          }
    
    

如下是TransactionCacheManager的源代码

        public class TransactionalCacheManager {
    
          //管理了多个Cache,每个Cache对应一个TransactionalCache
          private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
    
          //清空未commit()的临时数据
          public void clear(Cache cache) {
            getTransactionalCache(cache).clear();
          }
    
          //获取缓存数据
          public Object getObject(Cache cache, CacheKey key) {
            return getTransactionalCache(cache).getObject(key);
          }
    
          //设置缓存数据,数据应该被保存在临时区域,只commit才会保存在cache中
          public void putObject(Cache cache, CacheKey key, Object value) {
            getTransactionalCache(cache).putObject(key, value);
          }
    
          //数据临时数据刷新的Cache中,使用数据对其他的SqlSession对象也可见
          public void commit() {
            for (TransactionalCache txCache : transactionalCaches.values()) {
              txCache.commit();
            }
          }
    
          //回滚,应该是清除临时区域的数据
          public void rollback() {
            for (TransactionalCache txCache : transactionalCaches.values()) {
              txCache.rollback();
            }
          }
    
          //获取对应的TransactionalCache,没有就生成一个
          private TransactionalCache getTransactionalCache(Cache cache) {
            TransactionalCache txCache = transactionalCaches.get(cache);
            if (txCache == null) {
              txCache = new TransactionalCache(cache);
              transactionalCaches.put(cache, txCache);
            }
            return txCache;
          }
    
        }
    

再看看TransactionCache对象是怎么管理数据缓存数据的

        public class TransactionalCache implements Cache {
    
          private Cache delegate;
          //这个对象如果被设置为true,commit时Cache会先清除所有的数据
          private boolean clearOnCommit;
    
          //临时区域,提交时需要将数据刷新对Cache
          private Map<Object, AddEntry> entriesToAddOnCommit;
    
          //临时区域,提交时需要将数据从Cache中删除
          private Map<Object, RemoveEntry> entriesToRemoveOnCommit;
    
          public TransactionalCache(Cache delegate) {
            this.delegate = delegate;
            this.clearOnCommit = false;
            this.entriesToAddOnCommit = new HashMap<Object, AddEntry>();
            this.entriesToRemoveOnCommit = new HashMap<Object, RemoveEntry>();
          }
    
          @Override
          public String getId() {
            return delegate.getId();
          }
    
          @Override
          public int getSize() {
            return delegate.getSize();
          }
    
          @Override
          public Object getObject(Object key) {
            if (clearOnCommit) return null; // issue #146
            return delegate.getObject(key);
          }
    
          @Override
          public ReadWriteLock getReadWriteLock() {
            return null;
          }
    
          @Override
          public void putObject(Object key, Object object) {
            entriesToRemoveOnCommit.remove(key);
    
            //将数据放到临时区域,提交时再刷新到cache中
            entriesToAddOnCommit.put(key, new AddEntry(delegate, key, object));
          }
    
          @Override
          public Object removeObject(Object key) {
            entriesToAddOnCommit.remove(key);
    
            //将数据放到临时区域,提交时再从cache删除
            entriesToRemoveOnCommit.put(key, new RemoveEntry(delegate, key));
            return delegate.getObject(key);
          }
    
          @Override
          public void clear() {
            reset();
            clearOnCommit = true;
          }
    
          public void commit() {
            if (clearOnCommit) {
              //先清除所有的数据
              delegate.clear();
            } else {
              for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {
                //从cache中删除数据
                entry.commit();
              }
            }
            for (AddEntry entry : entriesToAddOnCommit.values()) {
              //将数据刷新到cache
              entry.commit();
            }
            reset();
          }
    
          public void rollback() {
            reset();
          }
    
          //清空临时区域
          private void reset() {
            clearOnCommit = false;
            entriesToRemoveOnCommit.clear();
            entriesToAddOnCommit.clear();
          }
    
          private static class AddEntry {
            private Cache cache;
            private Object key;
            private Object value;
    
            public AddEntry(Cache cache, Object key, Object value) {
              this.cache = cache;
              this.key = key;
              this.value = value;
            }
    
            public void commit() {
              //加数据
              cache.putObject(key, value);
            }
          }
    
          private static class RemoveEntry {
            private Cache cache;
            private Object key;
    
            public RemoveEntry(Cache cache, Object key) {
              this.cache = cache;
              this.key = key;
            }
    
            public void commit() {
              //删除数据
              cache.removeObject(key);
            }
          }
    
        }
    

总结以上代码重要的几点

  1. TransactionCache.put()方法是先将数据保存在临时的数据区域,并未在Cache加入数据
  2. TransactionCache.remove()方法是先在一个临时区域中保存要删除的数据,并未在Cache中删除数据
  3. TransactionCache.commit()方法将保存在临时区域的数据真正加入Cache中,将临时区域中需要删除的数据真正删除
  4. TransactionCache.rollback()方法,只是清除了临时区域中的数据
  5. TransactionCache.clear()方法,告诉commit()方法,先清除缓存的数据,再执行后续操作。但clear方法本身不会清除缓存中的数据

下面来看CachingExecutor是怎么利用这几个方法实现缓存的

        <p style="margin-top: 0px; margin-bottom: 0px; font-family: Monaco;"></p><pre name="code" class="java">public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
            BoundSql boundSql = ms.getBoundSql(parameterObject);
            //生成一个key
            CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
            return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          }
          public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
              throws SQLException {
            //从MappedStatement获取一个Cache,如果对象的命名空间没有配置cache或cache-ref节点,cache将为空,表示不使用缓存
            Cache cache = ms.getCache();
            if (cache != null) {
              //如果需要刷新缓存的话就刷新:flushCache="true"
              flushCacheIfRequired(ms);
              if (ms.isUseCache() && resultHandler == null) {
                //userCache="true"
                ensureNoOutParams(ms, parameterObject, boundSql);
                @SuppressWarnings("unchecked")
                //从Cache获取数据
                List<E> list = (List<E>) tcm.getObject(cache, key);
                if (list == null) {
                  //如果缓存中没有,就执行SQL生成数据
                  list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    
                  //将数据加入到临时区域
                  tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
                }
                return list;
              }
            }
            return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          }
    
    
    
        public int update(MappedStatement ms, Object parameterObject) throws SQLException {
            //如果需要刷新缓存的话就刷新:flushCache="true"
            flushCacheIfRequired(ms);
            return delegate.update(ms, parameterObject);
          }
    
        private void flushCacheIfRequired(MappedStatement ms) {
            Cache cache = ms.getCache();
            if (cache != null && ms.isFlushCacheRequired()) {  
              //commit()方法之后会清除所有的缓存    
              tcm.clear(cache);
            }
          }
    

小结

  1. 一级缓存只在一个SqlSession中有效,执行update/insert/delete语句后,一级缓存将会被清除。
  2. 二级缓存对所有的SqlSession有效,执行flushCache="true"的语句后,二级缓存将会被清除。
点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Mybatis3源码分析(17)-Sql解析执行-缓存的实现
上一篇
Mybatis3源码分析(16)-Sql解析执行-结果集映射(ResultSetHandler)
下一篇
Mybatis3源码分析(18)-插件(plugins)拦截器