• 深入理解MyBatis——缓存


    MyBatis中使用缓存来提高其性能。

    MyBatis中的缓存分为两种:一级缓存和二级缓存。使用过MyBatis的可能听到过这样一句话“一级缓存是sqlSession级别的,二级缓存是mapper级别的”。这也说明了,当使用同一个sqlSession时,查询到的数据可能是一级缓存;而当使用同一个mapper是,查询到的数据可能是二级缓存。

    MyBatis中的一级缓存

    执行查询时,SqlSession是将任务交给Executor来完成对数据库的各种操作,而Executor执行查询前,会先去查询缓存。

    Executor的实现类BaseExecutor.query方法源码

    @Override
      public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //动态的生成需要执行的sql语句,用BoundSql对象表示
        BoundSql boundSql = ms.getBoundSql(parameter);
        //获取一级缓存的key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    
      @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 {
              //缓存为空则去数据库查询
            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;
      }

    我们看一看Executor的实现BaseExecutor。

     

    BaseExecutor中拥有一个PerpetualCache,它是Cache接口的实现,则对于BaseExecutor对象而言,它将使用PerpetualCache对象维护缓存,Cache是一个缓存接口。

    PerpetualCache是如何实现对缓存的维护的?

    public class PerpetualCache implements Cache {
    
      private String id;
    
    //使用一个Map对象,作为缓存内容的容器
      private Map<Object, Object> cache = new HashMap<Object, Object>();
    
      public PerpetualCache(String id) {
        this.id = id;
      }
    
      public String getId() {
        return id;
      }
    
      public int getSize() {
        return cache.size();
      }
    
      public void putObject(Object key, Object value) {
        cache.put(key, value);
      }
    
      public Object getObject(Object key) {
        return cache.get(key);
      }
    
      public Object removeObject(Object key) {
        return cache.remove(key);
      }
    
      public void clear() {
        cache.clear();
      }
    
      public ReadWriteLock getReadWriteLock() {
        return null;
      }
    
      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());
      }
    
      public int hashCode() {
        if (getId() == null) throw new CacheException("Cache instances require an ID.");
        return getId().hashCode();
      }
    
    }

    PerpetualCache内部就是通过一个简单的HashMap

     
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        BoundSql boundSql = ms.getBoundSql(parameter);
        //创建cacheKey。
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
     }

    进入方法createCacheKey

     
    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
        if (closed) throw new ExecutorException("Executor was closed.");
        CacheKey cacheKey = new CacheKey();
        //获得statementId
        cacheKey.update(ms.getId());
        //获得rowBounds.offset
        cacheKey.update(rowBounds.getOffset());
        //获得rowBounds.Limit()
        cacheKey.update(rowBounds.getLimit());
        //获得boundSql.ql()
        cacheKey.update(boundSql.getSql());
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
        for (int i = 0; i < parameterMappings.size(); i++) { // mimic DefaultParameterHandler logic
          ParameterMapping parameterMapping = parameterMappings.get(i);
          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);
          }
        }
        return cacheKey;
      }    

    此时进入update方法:

    CacheKey.java

    public void update(Object object) {
        if (object != null && object.getClass().isArray()) {
          int length = Array.getLength(object);
          for (int i = 0; i < length; i++) {
            Object element = Array.get(object, i);
            doUpdate(element);
          }
        } else {
          doUpdate(object);
        }
      }
    
      private void doUpdate(Object object) {
        int baseHashCode = object == null ? 1 : object.hashCode();
    
        count++;
        checksum += baseHashCode;
        baseHashCode *= count;
    
        //产生hashcode
        hashcode = multiplier * hashcode + baseHashCode;
    
        updateList.add(object);
      }

    到这里,cacheKey的构建终于真相大白:根据 statementId 、 rowBounds 、传递给JDBC的SQL 和 rowBounds.limit决定key中的hashcode。因此,相同的操作就会有相同的hashcode,来保证一个cacheKey对应一个操作。

    MyBatis二级缓存

    待完善

  • 相关阅读:
    【HTTP】长连接和短连接
    HTTP/1.1 持久连接 persistent connection
    浅谈Websocket、Ajax轮询和长轮询(long polling)
    web通信之跨文档通信 postMessage
    HTML5 postMessage 和 localStorage 实现窗口间通信
    CORS 和 JSONP
    【转】js中几种实用的跨域方法原理详解
    [跨域]前端解决跨域问题
    RFC1867 HTTP file upload
    Multipart/form-data POST文件上传
  • 原文地址:https://www.cnblogs.com/huqingan/p/12940163.html
Copyright © 2020-2023  润新知