• mybatis源码(九)mybatis一级缓存的使用


    mybatis源码(九)mybatis一级缓存的使用

    mybatis缓存分为两种:一级缓存和二级缓存

    1.一级缓存:是sqlSession级别的缓存,同时mybatis的一级缓存不支持关闭

      例如通过<association>和<collection>建立级联映射、避免循环引用(circular references)、加速重复嵌套查询等)都是基于MyBatis一级缓存实现的,而且MyBatis结果集映射相关代码重度依赖CacheKey,所以目前MyBatis不支持关闭一级缓存。

      a.mybatis一级缓存localCacheScope属性

      • SESSION :缓存对整个sqlSession有效。只有当执行了update语句的时候,缓存才会被清除
      • STATEMENT :缓存仅对当前执行的语句有效。当语句执行完毕后,缓存才会被清除

      SqlSession提供了面向用户的API,真正执行操作的是Executor组件。

      b.mybatis一级缓存实现原理:

      mybatis的一级缓存实现了Cache接口。里面维护了一个map集合。

      cache接口的源码如下:

    public interface Cache {
    
      /**
       * @return The identifier of this cache
       */
      String getId();
    
      /**
       * @param key Can be any object but usually it is a {@link CacheKey}
       * @param value The result of a select.
       */
      void putObject(Object key, Object value);
    
      /**
       * @param key The key
       * @return The object stored in the cache.
       */
      Object getObject(Object key);
    
      /**
       * As of 3.3.0 this method is only called during a rollback 
       * for any previous value that was missing in the cache.
       * This lets any blocking cache to release the lock that 
       * may have previously put on the key.
       * A blocking cache puts a lock when a value is null 
       * and releases it when the value is back again.
       * This way other threads will wait for the value to be 
       * available instead of hitting the database.
       *
       * 
       * @param key The key
       * @return Not used
       */
      Object removeObject(Object key);
    
      /**
       * Clears this cache instance
       */  
      void clear();
    
      /**
       * Optional. This method is not called by the core.
       * 
       * @return The number of elements stored in the cache (not its capacity).
       */
      int getSize();
      
      /** 
       * Optional. As of 3.2.6 this method is no longer called by the core.
       *  
       * Any locking needed by the cache must be provided internally by the cache provider.
       * 
       * @return A ReadWriteLock 
       */
      ReadWriteLock getReadWriteLock();
    
    }
    View Code

      mybaits一级缓存PerpetualCache类的源码如下,可以看到缓存就是一个map集合

    public class PerpetualCache implements Cache {
    
      private final String id;
    
      private Map<Object, Object> cache = new HashMap<Object, Object>();
    
      public PerpetualCache(String id) {
        this.id = id;
      }
    
      @Override
      public String getId() {
        return id;
      }
    
      @Override
      public int getSize() {
        return cache.size();
      }
    
      @Override
      public void putObject(Object key, Object value) {
        cache.put(key, value);
      }
    
      @Override
      public Object getObject(Object key) {
        return cache.get(key);
      }
    
      @Override
      public Object removeObject(Object key) {
        return cache.remove(key);
      }
    
      @Override
      public void clear() {
        cache.clear();
      }
    
      @Override
      public ReadWriteLock getReadWriteLock() {
        return null;
      }
    
      @Override
      public boolean equals(Object o) {
        if (getId() == null) {
          throw new CacheException("Cache instances require an ID.");
        }
        if (this == o) {
          return true;
        }
        if (!(o instanceof Cache)) {
          return false;
        }
    
        Cache otherCache = (Cache) o;
        return getId().equals(otherCache.getId());
      }
    
      @Override
      public int hashCode() {
        if (getId() == null) {
          throw new CacheException("Cache instances require an ID.");
        }
        return getId().hashCode();
      }
    
    }
    View Code

      Cache接口的实现类如下:

      

       Cache接口使用装饰者模式,可以对某些Cache进行增强,比如说如下测试代码

    @Test
    public void testCache() {
        final int N = 100000;
        Cache cache = new PerpetualCache("default");
        cache = new LruCache(cache);
        cache = new FifoCache(cache);
        cache = new SoftCache(cache);
        cache = new WeakCache(cache);
        cache = new ScheduledCache(cache);
        cache = new SerializedCache(cache);
        cache = new SynchronizedCache(cache);
        cache = new TransactionalCache(cache);
        for (int i = 0; i < N; i++) {
            cache.putObject(i, i);
            ((TransactionalCache) cache).commit();
        }
        System.out.println(cache.getSize());
    }
    View Code

      也可以通过CacheBuilder生成器模式创建缓存对象,代码如下:

    @Test
    public void testCacheBuilder() {
        final int N = 100000;
        Cache cache = new CacheBuilder("com.blog4java.mybatis.example.mapper.UserMapper")
                .implementation( PerpetualCache.class)
                .addDecorator(LruCache.class)
                .clearInterval(10 * 60L)
                .size(1024)
                .readWrite(false)
                .blocking(false)
                .properties(null)
                .build();
        for (int i = 0; i < N; i++) {
            cache.putObject(i, i);
        }
        System.out.println(cache.getSize());
    }
    View Code

      mybatis中一级缓存的执行代码如下:

      BaseExecutor的部分源码如下:

    public abstract class BaseExecutor implements Executor {
    
      private static final Log log = LogFactory.getLog(BaseExecutor.class);
    
      protected Transaction transaction;
      protected Executor wrapper;
    
      protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
      // Mybatis一级缓存对象
      protected PerpetualCache localCache;
      // 存储过程输出参数缓存
      protected PerpetualCache localOutputParameterCache;
      protected Configuration configuration;
    
      protected int queryStack;
      private boolean closed;
    
      protected BaseExecutor(Configuration configuration, Transaction transaction) {
        this.transaction = transaction;
        this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
        // 创建一级缓存对象
        this.localCache = new PerpetualCache("LocalCache");
        this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
        this.closed = false;
        this.configuration = configuration;
        this.wrapper = this;
      }
    

      MyBatis通过CacheKey对象来描述缓存的Key值。在进行查询操作时,首先创建CacheKey对象 (CacheKey对象决定了缓存的Key与哪些因素有关系)。如果两次查询操作CacheKey对象相同,就认为这两次查询执行的是相同的SQL语句。CacheKey对象 通过BaseExecutor类的createCacheKey()方法创建,代码如下:

      @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        // 获取BoundSql对象,BoundSql是对动态SQL解析生成的SQL语句和参数映射信息的封装
        BoundSql boundSql = ms.getBoundSql(parameter);
        // 创建CacheKey,用于缓存Key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        // 调用重载的query()方法
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    
    
      @Override
      public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) {
          throw new ExecutorException("Executor was closed.");
        }
        CacheKey cacheKey = new CacheKey();
        cacheKey.update(ms.getId()); // Mapper Id
        cacheKey.update(rowBounds.getOffset()); // 偏移量
        cacheKey.update(rowBounds.getLimit()); // 条数
        cacheKey.update(boundSql.getSql()); // SQL语句
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        // 所有参数值
        for (ParameterMapping parameterMapping : parameterMappings) {
          if (parameterMapping.getMode() != ParameterMode.OUT) {
            Object value;
            String propertyName = parameterMapping.getProperty();
            if (boundSql.hasAdditionalParameter(propertyName)) {
              value = boundSql.getAdditionalParameter(propertyName);
            } else if (parameterObject == null) {
              value = null;
            } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
              value = parameterObject;
            } else {
              MetaObject metaObject = configuration.newMetaObject(parameterObject);
              value = metaObject.getValue(propertyName);
            }
            cacheKey.update(value);
          }
        }
        // Environment Id
        if (configuration.getEnvironment() != null) {
          cacheKey.update(configuration.getEnvironment().getId());
        }
        return cacheKey;
      }
    

      

    从上面的代码中。缓存key可能与下面的因素有关
      1.mapper的id。由mapper的命名空间的+<select|insert|update|delete> 的id值组合在一起
      2.查询结果的偏移量和查询的条数
      3.具体的sql语句及sql语句中需要传递的参数
      4.mybatis的主配置文件中。通过<envienoment>标签配置的环境信息的对应的id的属性值

    执行两次查询语句时,只有上面的信息完全相同时,才会认为两次查询执行相同的sql语句。缓存才会生效

    执行查询的方法

    @SuppressWarnings("unchecked")
      @Override
      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 {
            // 缓存中获取不到,则调用queryFromDatabase()方法从数据库中查询
            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
          }
        } finally {
          queryStack--;
        }
        if (queryStack == 0) {
          for (DeferredLoad deferredLoad : deferredLoads) {
            deferredLoad.load();
          }
          // issue #601
          deferredLoads.clear();
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            // issue #482
            clearLocalCache();
          }
        }
        return list;
      } 

          如上面的代码所示,在BaseExecutor类的query()方法中, 首先根据缓存Key从localCache属性中查找是否有缓存对象,如果查找不到,则调用queryFromDatabase()方法从数据库中获取数据,然后将数据
    写入localCache对象中。如果localCache中缓存了本次查询的结果,则直接从缓存中获取。需要注意的是,如果localCacheScope属性设置为STATEMENT,则每次查询操作完成后,都会调用clearlocalCache()
    方法清空缓存。除此之外,MyBatis会在执行完任意 更新语句后清空缓存,我们可以看一下BaseExecutor类的update()方法,代码如下:

      @Override
      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);
      }
    
    // 可以看到,MyBatis在调 用doUpdate()方法完成更新操作之前,首先会调用clearlocalCache()方法清空缓存。
    
      @Override
      public void clearLocalCache() {
        if (!closed) {
          localCache.clear();
          localOutputParameterCache.clear();
        }
      }

    在分布式环境下,务必将MyBatis的localCacheScope属性设置为STATEMENT,避免其他应用节点执行SQL更新语句后,本节点缓存得不到刷新而导致的数据一致性问题。

    3.mybatis支持使用redis、ehcache作为二级缓存

  • 相关阅读:
    Nginx配置文件nginx.conf中文详解
    PHP SOCKET编程 .
    当一个变量只能通过引用传递的时候。
    PHP-Socket-阻塞与非阻塞,同步与异步概念的理解
    PHP里的socket_recv方法解释
    jQuery获取Select选择的Text和 Value
    使用 cacti 批量监控服务器以及其 PHP 运作环境配置
    windows 和 linux 上 循环读取文件名称的区别和方法
    php 在linux 用fopen() 函数打开,file_get_contents(),fread()函数 读取 另外一台服务器映射过来的文件 总是返回false,null的情况。
    【问题解决】小数点前面不显示0的问题
  • 原文地址:https://www.cnblogs.com/yingxiaocao/p/13587927.html
Copyright © 2020-2023  润新知