zoukankan      html  css  js  c++  java
  • Mybatis一级缓存和二级缓存总结

    1:mybatis一级缓存:级别是session级别的,如果是同一个线程,同一个session,同一个查询条件,则只会查询数据库一次
    2:mybatis二级缓存:级别是sessionfactory级别的,是针对于各个线程发出的sql查询条件
    3:spring 关闭了mybatis的一级缓存,每一次查询都会建立一次连接,创建新的session,源码类有:
    MapperFactoryBean、SqlSessionDaoSupport、SqlSessionTemplate;在SqlSessionTemplate中invoke方法中,有提交,关闭连接等方法
    invoke方法是增强方法,实现原理也是动态代理
    4:spring集成mybatis的二级缓存中,有很多的坑:总结如下
    对于查询多commit少且用户对查询结果实时性要求不高,此时采用mybatis二级缓存技术降低数据库访问量,提高访问速度。
    但不能滥用二级缓存,二级缓存也有很多弊端,从MyBatis默认二级缓存是关闭的就可以看出来。
    二级缓存是建立在同一个namespace下的,如果对表的操作查询可能有多个namespace,那么得到的数据就是错误的。
    举个简单的例子:
    订单和订单详情,orderMapper、orderDetailMapper。在查询订单详情时我们需要把订单信息也查询出来,那么这个订单详情的信息被二级缓存在orderDetailMapper的namespace中,这个时候有人要修改订单的基本信息,那就是在orderMapper的namespace下修改,他是不会影响到orderDetailMapper的缓存的,那么你再次查找订单详情时,拿到的是缓存的数据,这个数据其实已经是过时的。
    根据以上,想要使用二级缓存时需要想好两个问题:
    1)对该表的操作与查询都在同一个namespace下,其他的namespace如果有操作,就会发生数据的脏读。
    2)对关联表的查询,关联的所有表的操作都必须在同一个namespace。

    ----------------------------------源码篇------------------------------------------------

    从configuaration中获取到MappedStatement,调用Executor的query方法实现执行

      @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.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
              tcm.putObject(cache, key, list); // issue #578 and #116
            }
            return list;
          }
        }
        return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
      }

    先查询二级缓存,是否有缓存,没有的话调用delegate.query方法,delegate是BaseExecutor执行器

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

    如果一级缓存中没有该结果,会调用queryFromDatabase查询数据库得到数据,然后再缓存到一级缓存中,下次查询的时候相同的sql语句直接走一级缓存不会查询数据库。
    Mybatis一级与二级缓存
    一级缓存
    mybatis的一级缓存是SqlSession级别的缓存,在操作数据库的时候需要先创建SqlSession会话对象,在对象中用一个HashMap来存储数据,此HashMap是当前会话对象私有的,别的SqlSession会话对象是无法访问的
    流程:

    • 第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来,key是查询的完成sql语句,里面有参数,不同的参数是不同的key
    • 第二次执行select会从缓存中查询数据,如果select相同并且参数一样,那么就能从缓存汇总返回数据,不用去查数据库了,从而提高效率

    注意事项:如果SqlSession执行了insert,update,delete并commit了,那么mybatis就会清空当前SqlSession中所有的一级缓存数据,这样可以保证缓存中存的数据永远和数据库一致,避免出现脏读。当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis是默认开启一级缓存的,不需要配置。当服务器集群的时候,每个sqlSesssion有自己独立的缓存,相互之间不共享,所以每个在服务器集群的时候mybatis的一级缓存会产生数据冲突问题。
    如何禁止一级缓存
    方案1 在sql语句上 随机生成 不同的参数 存在缺点:map集合可能爆
    方案2 开启二级缓存

    二级缓存
    二级缓存是mapper级别的缓存,当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个缓存区域。
    springboot项目中使用的配置
    在启动类中加上@EnableCaching注解
    在需要缓存的mapper中加上@CacheNamespace(implementation = MybatisRedisCache.class)这个注解和二级缓存的类就可以了
    TransactionalCache类
    继承Cache接口,主要作用是保存SqlSession在事务中需要向某个二级缓存提交的缓存数据(因为事务提交过程中 数据可能会回滚,所以不能直接把数据提交到二级缓存,而是暂存在TransactionCache中,在事务提交后再将过程中存放在其中的数据提交到二级缓存,如果事务回滚,则将数据清除)
    private Cache delegate; 对应的二级缓存对象
    private boolean clearOnCommit; 是否在commit时清除二级缓存的标记
    private Map<Object, Object> entriesToAddOnCommit;
    private Set entriesMissedInCache;
    TransactionalCacheManager
    用于管理CachingExecutor使用的二级缓存对象,只定义了一个transactionCaches字段。

    郭慕荣博客园
  • 相关阅读:
    php与nginx配置,不能运行php程序
    奇葩php之数组
    奇葩之mysql
    for语法研究
    php short tag不显示排查
    奇葩之mysql【三】我只想获得一个自增Id,我容易吗我
    男女不同
    Restart explorer
    iOS面试贴士
    phpmyadmin万能登陆密码
  • 原文地址:https://www.cnblogs.com/jelly12345/p/11985647.html
Copyright © 2011-2022 走看看