zoukankan      html  css  js  c++  java
  • mybatis 缓存

    【参考文章】:深入理解MyBatis——缓存

    【参考文章】:MyBatis 查询缓存

    【参考文章】:MyBatis -- 整合Redis二级缓存

    【参考文章】:MyBatis 随笔

    1. 简介

      MyBatis中的缓存分为两种:一级缓存和二级缓存。

      一级缓存:sqlSession级别,当使用同一个sqlSession时,查询到的数据可能是一级缓存;

      二级缓存:mapper级别,当使用同一个mapper时,查询到的数据可能是二级缓存。

            a. 为每一个Mapper分配一个Cache缓存对象(使用<cache>节点配置)
            b. 多个Mapper共用一个Cache缓存对象(使用<cache-ref>节点配置)

      查询顺序:二级缓存 -->  一级缓存 -->  数据库

    2. 缓存相关的默认设置

    2.1 查询语句

      select 标签的 useCache 属性:

      默认为 true,其结果被二级缓存。

    2.2 数据变更语句

      insert,update,delete 的 flushCache属性:

      默认为true,数据变更语句被调用,都会导致本地缓存和二级缓存都会被清空。

    2.3 二级缓存配置

      默认情况下是没有开启缓存的,除了局部的 session 缓存,可以增强变现而且处理循环 依赖也是必须的。要开启二级缓存,你需要在你的 SQL 映射文件中添加一行:

    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="1024"
      readOnly="true"/>

    3. 一级缓存

       MyBatis默认一级查询缓存是开启状态,且不能关闭。

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

      BaseExecutor 的 query();

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

      BaseExecutor 的缓存维护:

      使用 cache 接口的实现类  PerpetualCache 

      protected PerpetualCache localCache;
      protected PerpetualCache localOutputParameterCache;

      PerpetualCache 的实现原理:

      内部维护一个 HashMap

    public class PerpetualCache implements Cache {
    
      private Map<Object, Object> cache = new HashMap<Object, Object>();
    
    }

      HashMap的键为CacheKey,

      cacheKey的构建终于真相大白:根据 statementId 、 rowBounds.offset 、rowBounds.limit 和 SQL的参数 决定key中的hashcode。

      因此,相同的操作就会有相同的hashcode,来保证一个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();
    }

      update 方法:

      public void update(Object object) {
        int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 
    
        count++;
        checksum += baseHashCode;
        baseHashCode *= count;
    
        hashcode = multiplier * hashcode + baseHashCode;
    
        updateList.add(object);
      }

    4. 二级缓存

      二级缓存开始时 实体类必须实现 Serializable 接口;

    4.1 缓存策略实现原理

          

      mybatis 二级缓存策略通过装饰者模式实现

      二级缓存策略在 一级缓存策略 PerpetualCache 的基础上通过装饰进行功能增强。

    4. 缓存测试

    4.1 一级缓存是否存在,数据变更是否刷新缓存

        private static String base = "com.skd.entity.UserMapper.";
        public static void main(String[] args)
        {
            testCacheExist(23,false);
    
        }
        private static void testCacheExist(int id, boolean flush)
        {
            System.out.println();
            SqlSession session = ConnectUtil.getSession();
            String namespace = base + "getUser";
            User user = (User)session.selectOne(namespace, id);
            System.out.println(user);
    
            if(flush){
                String namespace2 = base + "updateUser";
                user.setName("flush");
                session.update(namespace2, user);
            }
            user = (User)session.selectOne(namespace, id);
            System.out.println(user);
        }

       flush 为 false 时:

      第一次执行SQL语句查询;

      第二次没有执行SQL语句,说明是在缓存中查询的

      

       flush 为 true 时:

      第一次执行SQL语句查询;

      第二次执行SQL语句查询,

      

      结论:

      一级缓存存在;

      数据变更会刷新缓存;

    4.2 一级缓存是针对同一个sqlSession吗?

        private static void differentSession(int id)
        {
            String namespace = base + "getUser";
    
            SqlSession session = ConnectUtil.getSession();
            User user1 = (User)session.selectOne(namespace, id);
            System.out.println(user1);
            session.close();
    
            SqlSession session2 = ConnectUtil.getSession();
            User user2 = (User)session2.selectOne(namespace, id);
            System.out.println(user2);
            session2.close();
        }

       测试结果:

      

       结论:

      不同的 sqlSession 查询时会执行SQL查询,不会存缓存中查询。

      一级缓存针对的时同一个 sqlSession 。

    4.3 二级缓存测试

      开启二级缓:

      全局配置:

    <configuration>
    
        <settings>
            <setting name="cacheEnabled" value="true" />
            <!-- 打印查询语句 -->
            <setting name="logImpl" value="STDOUT_LOGGING" />
        </settings>
    
    </configuration>

      SQL映射文件配置:

    <mapper namespace="com.skd.entity.UserMapper">
    
        <cache></cache>
    
    </mappe>

       测试代码:

        private static void differentSession(int id)
        {
            String namespace = base + "getUser";
    
            SqlSession session = ConnectUtil.getSession();
            User user1 = (User)session.selectOne(namespace, id);
            System.out.println(user1);
            session.close();
    
            SqlSession session2 = ConnectUtil.getSession();
            User user2 = (User)session2.selectOne(namespace, id);
            System.out.println(user2);
        }

      注意:如果不把session关掉,查询结果是不会设置到缓存中的,一个缓存一次只允许一个会话操作。

      因为考虑到如果两个会话同时操作一份缓存,会引起线程并发修改的问题,所以要关掉最先查询的那个session,查询结果才会被写到二级缓存中。 

  • 相关阅读:
    spring scope 属性的取值
    DefaultTransactionStatus源码
    Spring事务管理接口PlatformTransactionManager的实现类DataSourceTransactionManager
    Spring 框架简介
    PL/SQL游标
    [BC冠军赛(online)]小结
    [hdu5164]ac自动机
    [hdu2222]ac自动机(模板)
    上浮法或漂浮法
    [hdu5213]容斥原理+莫队算法
  • 原文地址:https://www.cnblogs.com/virgosnail/p/10054208.html
Copyright © 2011-2022 走看看