mybatis缓存机制二

mybatis缓存机制二,第1张

mybatis缓存机制二 mybatis二级缓存

二级缓存的使用

mybatis 二级缓存不默认开启,需要手动配置,因为二级缓存是mapper级别的,所以不同的mapper可以使用不同的缓存策略。

二级缓存开启需要三个步骤

  1. config.xml中需要配置,这个配置默认为true所以可以省略:

    
    
    
    
    

上述配置对应的源码解析和使用:

像核心配置文件中的配置肯定是在mybatis第一步 *** 作,只有解析完配置文件才能后续的 *** 作,而且mybatis解析完都会把配置存到Configuration对象中,这里对缓存配置的代码如下图: XMLConfigbuilder:

根据代码显示默认配置就是true,所以这里的配置是可以省略的。

那在哪里用到了这个配置呢?
SqlSessionFactory在的openSession时候,会实例化一个Executor对象,如下图:


在上图代码中当cacheEnabled为true,就会实例化一个CachingExecutor对象(这里用到了装饰器的设计模式,对于设计模式这里不做赘述了),这个CachingExecutor就是对缓存使用的执行器。

  1. 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 entriesToAddOnCommit;
      
      @Override
      public void putObject(Object key, Object object) {
        entriesToAddOnCommit.put(key, object);
      }  
    

    那么问题来了,二级缓存取数据是从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 entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
    }
    

    以上是个人对mybatis二级缓存的学习心得,如有模糊之处,忘不吝赐教!

    欢迎分享,转载请注明来源:内存溢出

    原文地址: http://outofmemory.cn/zaji/5437905.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-11
下一篇 2022-12-11

发表评论

登录后才能评论

评论列表(0条)

保存