Hello World

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,查询结果才会被写到二级缓存中。 

posted @ 2018-12-02 17:21  小小忧愁米粒大  阅读(503)  评论(0编辑  收藏  举报
瞅啥瞅,好好看书