Mybatis一级缓存

  1. 为什么要用一级缓存?

Mybatis在开始使用一次数据库的时候,会创建一个新的SqlSession,简称一次会话。

在对数据库的一次会话中,有时候会反复快速地执行完全相同的查询语句,如果没一级缓存的话,每一次查询都会查询一次数据库,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。如果使用一级缓存的话,会将每次查询结果缓存起来,当下次查询的时候,会判断是否存在完全一样的查询,存在的话直接从缓存中将结果取出,返回给用户,不存在的话再去数据库中查,接着将查询结果缓存起来。 

    2. Mybatis一级缓存怎么存储的?

其实每次Mybatis的查询与更新都是Executor这个接口来完成, Executor的实现类BaseExecutor中拥有一个Cache接口的实现类PerpetualCache,如下所示: 

 

 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++;
      //每次查询都会调用PerpetualCache的getObject()方法来验证先前是否有相同的查询
      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;
  }
//如果先前有相同的查询,会调用这个方法
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
      //调用PerpetualCache的getObject()的方法来获取相同查询的查询结果
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
          if (parameterMapping.getMode() != ParameterMode.IN) {
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }

 

所以Mybatis的一级缓存实际上是 PerpetualCache来实现,PerpetualCache其实也是靠HashMap来存储一级缓存,代码如下:

public class PerpetualCache implements Cache {
 
  private String id;
 
  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);
  }
 //Mybatis的任何一个update操作(update()、delete()、insert()) ,都会
 //调用这个方法,清空PerpetualCache对象存储的一级缓存数据
  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();
  }
 
}

 3. 怎么判断查询是否相同? 

PerpetualCache将每次查询的特征作为key,查询的结果作为value存储到Map中。所以看下key是怎么生成的,就知道怎么判断了。通过源码,发现key是通过BaseExecutor的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());
    //SQL语句
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    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);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

 从上面的代码中可以看出,如果下面条件一样,就可以判断为两个相同的查询,条件如下:

       1、statementId ;2、RowBounds的offset、limit的结果集分页属性;3、SQL语句;4、传给JDBC的参数值

对了还有一点,添加一些无关的参数值(比如添加Student的age的参数,但查询条件没有age这个参数),还是相同的两个查询;只要statementId,rowBounds,最后生成的SQL语句,以及这个SQL语句所需要的参数完全一致就是两个相同的查询。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值