zoukankan      html  css  js  c++  java
  • mybatis——缓存

    前面研究Executor时,里面有一级缓存和二级缓存的使用,这里深入研究一下缓存

    一、一级缓存

    1、一级缓存初始化

    一级缓存指的是在BaseExecutor初始化的时候创建的,是一个PerpetualCache类型的对象.

      protected BaseExecutor(Configuration configuration, Transaction transaction) {
        ...
        this.localCache = new PerpetualCache("LocalCache");//实际就是BaseExecutor.localCache
        ...
      }
    
      private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ...
        localCache.putObject(key, list);//实际数据存放在PerpetualCache.cache中这个是一个Map<Object, Object>类型。
        ...
      }

    一级缓存的初始化与数据存放比较简单,SqlSessionFactory.openSession()每创建一个Session时就会创建一个Executor,一级缓存存储在BaseExecutor.localCache中

    /* org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource */
      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);
          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();
        }
      }

    2、总结一下

    对应关系:是一个sqlSession对应一个Executor,一个BaseExecutor创建一个一级缓存localCache,然后结合上文的update()、事务提交回退都会清空一级缓存localCache,因此

    ① 同个session的两次相同查询:mybatis只查询一次数据库

    ② 同个session的两次不同查询:mybatis查询两次数据库

    ③ 同个session一个查询、一次更新、然后执行相同查询:mybatis查询两次数据库

    ④ 同个session一次查询、一次事务提交、然后执行相同查询:mybatis查询两次数据库

    ⑤ 不同session的两次相同查询:mybatis查询两次数据库

    二、二级缓存

    1、二级缓存的初始化

    二级缓存对应的Configuration.caches是一个Map<String,Cache>类型的对象。Map中的<String,Cache>实例是在解析mapper.xml中的<cache>标签时创建的。即一个mapper.xml对应一个cache。所以尽管mybatis默认的Executor是new CachingExecutor(new SimpleExecutor),但是<mapper>中没加<cache>标签的话,二级缓存默认还是关闭的。

    /* org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType) */
      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);
        }
        if (cacheEnabled) {//默认为true
          executor = new CachingExecutor(executor);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }

    mapper.xml中 <mapper>标签解析+<cache>标签解析

    /* org.apache.ibatis.builder.xml.XMLMapperBuilder#parse */
      public void parse() {
        if (!configuration.isResourceLoaded(resource)) {
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }
    
      private void configurationElement(XNode context) {
        try {
          String namespace = context.getStringAttribute("namespace");
          if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
          builderAssistant.setCurrentNamespace(namespace);
          cacheRefElement(context.evalNode("cache-ref"));
          cacheElement(context.evalNode("cache"));
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          sqlElement(context.evalNodes("/mapper/sql"));
          buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
        }
      }
    
      private void cacheElement(XNode context) throws Exception {
        if (context != null) {
          String type = context.getStringAttribute("type", "PERPETUAL");
          Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
          String eviction = context.getStringAttribute("eviction", "LRU");
          Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
          Long flushInterval = context.getLongAttribute("flushInterval");
          Integer size = context.getIntAttribute("size");
          boolean readWrite = !context.getBooleanAttribute("readOnly", false);
          boolean blocking = context.getBooleanAttribute("blocking", false);
          Properties props = context.getChildrenAsProperties();
          builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
        }
      }
    
    /* org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache */
      public Cache useNewCache(Class<? extends Cache> typeClass,
          Class<? extends Cache> evictionClass,
          Long flushInterval,
          Integer size,
          boolean readWrite,
          boolean blocking,
          Properties props) {
    //建造器模式创建cache Cache cache
    = new CacheBuilder(currentNamespace) .implementation(valueOrDefault(typeClass, PerpetualCache.class)) .addDecorator(valueOrDefault(evictionClass, LruCache.class)) .clearInterval(flushInterval) .size(size) .readWrite(readWrite) .blocking(blocking) .properties(props) .build(); configuration.addCache(cache); currentCache = cache; return cache; }

    2、二级缓存出现的原因

    源自官网在一级缓存中,不同session进行相同SQL查询的时候,是查询两次数据库的。显然这是一种浪费,既然SQL查询相同,就没有必要再次查库了,直接利用缓存数据即可,这种思想就是MyBatis二级缓存的初衷。

    另外,Spring和MyBatis整合时,每次查询之后都要进行关闭sqlsession,关闭之后数据被清空。所以MyBatis和Spring整合之后,一级缓存是没有意义的。如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到mapper namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。

    3、二级缓存的使用

    <mapper namespace="org.study.mapper.UserMapper">
    <!-- 在mapper下加入cache标签 -->
    <!-- <cache/> -->

    由于上面说的spring+mybatis每次查询都会关闭sqlSession,所以一级缓存没有意义,连续两次查询

     加上<cache/>后

     补充一下属性讲解:

    <cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>

    eviction="LRU":缓存过期置换策略,其实默认的是LRU(LruCache),还有FIFO(FIFOCache)多个装饰者模式的增强PerpetualCache的Cache。

    flushInterval="60000":缓存过期时间,装饰者模式,外面加了一层ScheduleCache。控制缓存的有效时间。

    size="512":缓存大小,判断Map.size(),不是内存大小是key的个数。

    readOnly="true":只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。速度上会慢一些,但是更安全,因此默认值是 false。

    4、总结

    ① 二级缓存的目的:解决一级缓存不能跨sqlSession的弊病,二级缓存可以跨sqlSession。

    ② 二级缓存默认是关闭的,默认开启的是CachingExecutor,所以仅需要在mapper.xml中设置<cache/>标签的可以开启二级缓存了。

    ③ 一级缓存二级缓存的cacheKey相同,但是一级缓存对应一个sqlSession,二级缓存对应一个mapper.xml命名空间(可跨sqlSession)。

    ④ 写操作都会引起缓存清空。但一级缓存是整体清空,二级缓存清空的是操作的mapper.xml命名空间下对应的缓存,其他命名空间不受影响

    ⑤ 过期策略。由于最终存储对象都是PerpetualCache中的Map中,所以理论上是都不会过期的。但是一级缓存flush频繁,二级缓存可以通过flushInterval设置过期时间,需要注意的是flushInterval对自定义的Cache不起作用。

    ⑥ 默认的二级缓存是是JVM层面的缓存,在分布式架构中应用不佳,不能保证缓存数据的一致性。但是可以自定义分布式cache,例如使用redis实现二级缓存。

    三、mybatis缓存结构

    默认单机:

    分布式:

     

     四、补充

    1、redis实现分布式二级缓存

    package org.study.mappercache;
    
    import com.alibaba.fastjson.JSONObject;
    import com.fasterxml.jackson.annotation.JsonFormat;
    import com.google.gson.JsonObject;
    import com.google.gson.JsonParser;
    import org.apache.ibatis.cache.Cache;
    import org.springframework.util.StringUtils;
    import org.study.redis.RedisUtils;
    import redis.clients.jedis.params.SetParams;
    
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class MappedRedisCache implements Cache {
    
        /**
         * redisCache 装饰者模式:增强过期时间控制
         */
        private Cache cache;
    
        public MappedRedisCache(String id){
            cache = new RedisCache("mybatis_" + id);
        }
    
        @Override
        public String getId() {
            return cache.getId();
        }
    
        @Override
        public void putObject(Object key, Object value) {
            if (key != null && value != null){
                //key转hashCode存储 节省空间
                RedisUtils.getJedisCluster().set(key.toString().hashCode()+"","1",new SetParams().ex(600));
                cache.putObject(key,value);
            }
        }
    
        @Override
        public Object getObject(Object key) {
            if (key == null){
                return null;
            }
            if(!StringUtils.isEmpty(RedisUtils.getJedisCluster().get(key.toString()))){
                RedisUtils.getJedisCluster().set(key.toString().hashCode()+"","1",new SetParams().ex(600));
                cache.getObject(key);
            }
            cache.removeObject(key);
            return null;
        }
    
        @Override
        public Object removeObject(Object key) {
            if (key == null){
                return null;
            }
            RedisUtils.getJedisCluster().del(key.toString());
            Object obj =  cache.getObject(key);
            cache.removeObject(key);
            return obj;
        }
    
        @Override
        public void clear() {
            Set<String> keySet = RedisUtils.getJedisCluster().hkeys(getId());
            if(keySet != null && keySet.size() != 0){
                keySet.forEach(key -> RedisUtils.getJedisCluster().del(key));
            }
            cache.clear();
        }
    
        @Override
        public int getSize() {
            return cache.getSize();
        }
    
        @Override
        public ReadWriteLock getReadWriteLock() {
            return cache.getReadWriteLock();
        }
    
        private class RedisCache implements Cache{
    
            private String id;
            public RedisCache(String id){
                this.id = id;
            }
            @Override
            public String getId() {
                return id;
            }
    
            @Override
            public void putObject(Object key, Object value) {
                if (key != null && value != null){
                    RedisUtils.getJedisCluster().hset(getId(),key.toString().hashCode()+"",JSONObject.toJSONString(value));
                }
            }
    
            @Override
            public Object getObject(Object key) {
                if (key != null){
                    return RedisUtils.getJedisCluster().hget(getId(),key.toString());
                }
                return null;
            }
    
            @Override
            public Object removeObject(Object key) {
                if (key != null){
                    return RedisUtils.getJedisCluster().hdel(getId(),key.toString());
                }
                return null;
            }
    
            @Override
            public void clear() {
                RedisUtils.getJedisCluster().del(getId());
            }
    
            @Override
            public int getSize() {
                return RedisUtils.getJedisCluster().hlen(getId()).intValue();
            }
    
            @Override
            public ReadWriteLock getReadWriteLock() {
                return this.lock;
            }
    
            private final ReadWriteLock lock = new ReentrantReadWriteLock();
        }
    
    }
  • 相关阅读:
    316 Remove Duplicate Letters 去除重复字母
    315 Count of Smaller Numbers After Self 计算右侧小于当前元素的个数
    313 Super Ugly Number 超级丑数
    312 Burst Balloons 戳气球
    309 Best Time to Buy and Sell Stock with Cooldown 买股票的最佳时间含冷冻期
    Java 类成员的初始化顺序
    JavaScript 全局
    HTML字符实体
    Java中的toString()方法
    JavaScript 弹窗
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12456799.html
Copyright © 2011-2022 走看看