五、MyBatis缓存

一、MyBatis缓存介绍

缓存的使用可以明显的加快访问数据速度,提升程序处理性能,生活和工作中,使用缓存的地方很多。在开发过程中,从前端-->后端-->数据库等都涉及到缓存。MyBatis作为数据访问框架,也提供了缓存功能,分别为:一级缓存和二级缓存。在使用MyBatis的时候,显示或者默认的使用了缓存。缓存虽好,但是如果随便使用,可能会导致很多问题,因此,有必要了解MyBatis缓存的底层工作原理。

二、一级缓存

1、工作原理

在应用程序运行时,在一个数据库事务中,很可能一个查询SQL运行多次,这里就可以直接从缓存中拿数据,而不必去查询数据库。因此,这里MyBatis就设置了一级缓存,来提高查询效率。其流程如下:

对应的时序图如下:

缓存未命中时序图:

缓存命中:

缓存key的生成规则为:MappedStatement.id+offset+limit+sql+property+environment,只有在key完全相同的情况下,才会被认为是命中。

cacheKey的一个样例值:

461375566:-715040544:com.iwill.mybatis.dao.mapper.ext.UserMapperExt.findUserListByName:0:2147483647:SELECT * FROM CNT_USER WHERE name =?:zhangsan:SqlSessionFactoryBean

cacheKey的生成源码如下:

 @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());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    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;
  }

一级缓存的查询代码如下:


  @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;
  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

2、注意事项

a、一级缓存默认是开启的,且其适用范围为SESSION,其使用范围是同一个SqlSession中的同一个事务中,跨事务的查询是不会走到一级缓存的,因为在第一个事务提交(还有insert、update、delete等操作)时,会执行clearLocalCache操作,这里面会去清空一级缓存。

b、默认的一级缓存存在一个问题,如果在同一个事务A中,两个相同SQL执行中间,另外一个事务提交了数据,那么事务A中的第二次查询就会读取到脏数据(直接读缓存,未读取数据库)。对数据敏感的业务,需要设置一级缓存的适用范围为STATEMENT,这样,每次执行一个sql,都会清空一级缓存(相当于关闭了一级缓存),下次查询时,就会去查询数据库了。

三、二级缓存

1、工作原理

二级缓存是范围更广的缓存,其适用范围是一个namespace(这个namespace就是一个mapper xml文件),也可以多个namespace共享一个二级缓存。其执行流程如下:

二级缓存执行代码如下:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

默认情况下,二级缓存是关闭的,需要主动打开二级缓存,配置方式如下:

a、在mybatis-config.xml中设置cacheEnabled=true(默认情况下,cacheEnabled也是true):

    <settings>
        <setting name="localCacheScope" value="SESSION"/>
        <setting name="cacheEnabled" value="true"/>
   <!--     &lt;!&ndash;开启驼峰式命名,数据库的列名能够映射到去除下划线驼峰命名后的字段名&ndash;&gt;
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <setting name="logImpl" value="LOG4J"/>-->
    </settings>

b、在mapper xml中配置cache标签

<cache/>

2、注意事项

a、二级缓存是范围更加广泛的缓存,针对namespace级别,也可以跨namespace,这样也更加容易产出脏数据,因此,一般不会开启二级缓存。

b、二级缓存默认关闭,如果需要打开,则需要单独配置,如果需要打开所有namespace的二级缓存,目前MyBatis还不支持这样的配置,可以进行优化(意义可能不大,因为二级缓存使用的少)。

四、写在最后

缓存就如一把双刃剑,有优点也有缺点,关键在于怎么使用。优点就是可以提高数据访问速度,缺点就是缓存中数据可能和实际的数据(数据库、远程应用数据等)不一致。俗话说,任何脱离业务场景的架构都是耍流氓,对于缓存的使用,同样适用。

我们应该结合我们具体的业务场景来使用缓存,针对本文,MyBatis的一二级缓存,如果数据只查询,不更新,那么完全没问题。

项目地址:https://github.com/yangjianzhou/mybatis-demo

 

参考:

1、聊聊MyBatis缓存机制

2、mybatis

转载于:https://my.oschina.net/yangjianzhou/blog/3028349

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值