Mybatis缓存学习

mybatis缓存核心代码:

 ```java
// executor 是cachingExecutor
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

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

// 有缓存直接使用缓存,没有则查询DB
@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);
  }

一级缓存

一级缓存默认开启,无法关闭

@Test
public void defaultLocalCache(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
    List<UserLogin> list = mapper.queryByNameLocalCache("核弹");
    for (UserLogin e : list) {
        System.out.println(e);
    }
    System.out.println("\n========================================\n");
    List<UserLogin> list2 = mapper.queryByNameLocalCache("核弹");
    for (UserLogin e : list2) {
        System.out.println(e);
    }
}

第二次查询直接使用缓存,查询的结果使用相同的引用
在这里插入图片描述

一级缓存的禁用

/**
* 本地缓存无法通过设置来禁用,
* 可以使用 flushCache 强制刷新,来达到与禁用缓存相同的效果
* <select id="queryByNameNoLocalCache" resultMap="BaseMap" flushCache="true">    
*/
@Test
public void disableLocalCache(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
    List<UserLogin> list = mapper.queryByNameNoLocalCache("核弹");
    for (UserLogin e : list) {
        System.out.println(e);
    }
    System.out.println("\n===========================================\n");
    List<UserLogin> list2 = mapper.queryByNameNoLocalCache("核弹");
    for (UserLogin e : list2) {
        System.out.println(e);
    }
}

每次查询都会flush cache,重新查询DB,所以测试执行两次DB查询,查询结果是完全不同的对象
在这里插入图片描述

增删改操作会清空一级缓存

@Test
   public void queryWithSave(){
       SqlSession sqlSession = sqlSessionFactory.openSession();
       UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
       List<UserLogin> list = mapper.queryByNameLocalCache("核弹");
       for (UserLogin e : list) {
           System.out.println(e);
       }
       UserLogin save = new UserLogin("核弹", "淮南", "172.0.0.2");
       mapper.save(save);
       System.out.println("\n========================\n");
       List<UserLogin> list2 = mapper.queryByNameLocalCache("核弹");
       for (UserLogin e : list2) {
           System.out.println(e);
       }
   }

仍然查询两次数据库
在这里插入图片描述

源码分析:

  1. 三个方法底层都是调用DefaultSQLSession#update方法
    在这里插入图片描述
    在这里插入图片描述
  2. 执行器executor的update方法会清空本地缓存
    在这里插入图片描述

二级缓存

默认禁用二级缓存

/**
* 默认禁用二级缓存
*/
@Test
public void defaultDisableLevel2Cache(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
    List<UserLogin> list = mapper.queryByNameLocalCache("核弹");
    for (UserLogin e : list) {
        System.out.println(e);
    }
    System.out.println("\n==================SQLSession中=================\n");
    List<UserLogin> oneMore = mapper.queryByNameLocalCache("核弹");
    for (UserLogin e : oneMore) {
        System.out.println(e);
    }
    System.out.println("\n==================新的SQLSession=================\n");
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    UserLoginMapper mapper2 = sqlSession2.getMapper(UserLoginMapper.class);
    List<UserLogin> list2 = mapper2.queryByNameLocalCache("核弹");
    for (UserLogin e : list2) {
        System.out.println(e);
    }
}

在这里插入图片描述

开启二级缓存

mybatis全局配置 cache-enabled: true 或 标签
具体的statement 使用 useCache = true,使本地一级缓存变为二级缓存(即作用域由SQLSession提示为namespace)

/**
* <cache/><!--开启全局缓存-->
* <select id="queryByNameLevel2Cache" resultMap="BaseMap" useCache="true">
* 启用二级缓存(作用域为namespace)
*/
@Test
public void enableLevel2Cache(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
    List<UserLogin> list = mapper.queryByNameLevel2Cache("核弹");
    for (UserLogin e : list) {
        System.out.println(e);
    }
    System.out.println("\n========相同的SQLSession=======\n");
    List<UserLogin> oneMore = mapper.queryByNameLevel2Cache("核弹");
    for (UserLogin e : oneMore) {
        System.out.println(e);
    }
    // session如果不关闭,二级缓存无法启用
    sqlSession.close();
    System.out.println("\n========新的SQLSession=======\n");
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    UserLoginMapper mapper2 = sqlSession2.getMapper(UserLoginMapper.class);
    List<UserLogin> list2 = mapper2.queryByNameLevel2Cache("核弹");
    for (UserLogin e : list2) {
        System.out.println(e);
    }
}

只查询了一次数据库
在这里插入图片描述
二级缓存中的对象与一级缓存中的对象引用不同,二级缓存涉及到对象的序列化与反序列化,所以得到的是新的对象。

自定义二级缓存

通过mybatis源码可以看到,mybatis内部的几种二级缓存实现都是继承Cache接口,这是实现自定义缓存的关键.
无论是mybatis默认的二级缓存实现,还是与第三方集成(如 Redis,ehcache),因为实现了同一接口,其基本流程都是类似的: 将一次查询结果作为value,将查询参数,namespace等组合生成key,不同的二级缓存实现,k-v存储的逻辑不同.

与ehcache集成

  1. 导入依赖
<dependency>
  <groupId>org.mybatis.caches</groupId>
  <artifactId>mybatis-ehcache</artifactId>
  <version>1.2.2</version>
</dependency>
  1. 指定二级缓存的具体实现,且开启二级缓存
<!--<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>-->
<select id="getByName" resultMap="BaseMap" useCache="true">

与Redis集成

  1. 导入依赖
<dependency>
	<groupId>org.mybatis.caches</groupId>
	<artifactId>mybatis-redis</artifactId>
	<version>1.0.0-beta2</version>
</dependency>
  1. 指定二级缓存的具体实现,且开启二级缓存
<cache type="org.mybatis.caches.redis.RedisCache"/>
<select id="getByName" resultMap="BaseMap" useCache="true">
	select * from student where name like concat('%',#{name},'%')
</select>
  1. mybatis-redis 中,Redis的连接不是通过yml,而是新建redis.properties文件进行设置
    在这里插入图片描述
@Test
    public void test01(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> list = mapper.getByName("核弹");
        for (Student student : list) {
            System.out.println(student);
        }
        System.out.println("same session---------");
        List<Student> list2 = mapper.getByName("核弹");
        for (Student student : list2) {
            System.out.println(student);
        }

        System.out.println("same namespace-----------");
        sqlSession.commit();

        SqlSession sqlSession3 = sqlSessionFactory.openSession();
        StudentMapper mapper3 = sqlSession3.getMapper(StudentMapper.class);
        List<Student> list3 = mapper3.getByName("核弹");
        for (Student student : list3) {
            System.out.println(student);
        }
    }

首次查询
在这里插入图片描述
第二次查询
原因是数据已经被写入Redis,缓存中有,则直接返回.
在这里插入图片描述

注意

  1. 被缓存的对象需要实现Serializable接口,否则开启二级缓存会报序列化错误。因为二级缓存我们是可以存到内存外的地方,必然会涉及序列化问题。
  2. SQLSession需要关闭或提交后,新的SQLSession才能读到二级缓存
    在未提交前,SQLSession中的二级缓存都是暂存在transactionCacheManager中,只有提交后调用PerpetualCache.putObject放入map中,才能被其他SQLSession读取

useCache 和 flushCache的说明

  • useCache只与二级缓存开启有关
  • flushCache清除缓存,不分一级还是二级
    例如:<select id = "xx" flushCache = "true"></select>
    表示只有一级缓存,每次查询结束都会清空缓存,因为只有一级缓存,所以被清空的只有一级缓存
    <select id = "xx" useCache = "true" flushCache = "true"></select>
    表示开启二级缓存,一级缓存默认开启,每次查询结束清空缓存,此时存在的一级和二级缓存都会被清空
// <select id="queryByNameLevel2Cache" useCache="true" flushCache="true">
public void enableLevel2Cache(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
    List<UserLogin> list = mapper.queryByNameLevel2Cache("核弹");
    for (UserLogin e : list) {
        System.out.println(e);
    }
    System.out.println("\n========相同的SQLSession=======\n");
    List<UserLogin> oneMore = mapper.queryByNameLevel2Cache("核弹");
    for (UserLogin e : oneMore) {
        System.out.println(e);
    }
    // session如果不关闭,二级缓存无法启用
    sqlSession.close();
    System.out.println("\n========新的SQLSession=======\n");
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    UserLoginMapper mapper2 = sqlSession2.getMapper(UserLoginMapper.class);
    List<UserLogin> list2 = mapper2.queryByNameLevel2Cache("核弹");
    for (UserLogin e : list2) {
        System.out.println(e);
    }
}public void enableLevel2Cache(){
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserLoginMapper mapper = sqlSession.getMapper(UserLoginMapper.class);
    List<UserLogin> list = mapper.queryByNameLevel2Cache("核弹");
    for (UserLogin e : list) {
        System.out.println(e);
    }
    System.out.println("\n========相同的SQLSession=======\n");
    List<UserLogin> oneMore = mapper.queryByNameLevel2Cache("核弹");
    for (UserLogin e : oneMore) {
        System.out.println(e);
    }
    // session如果不关闭,二级缓存无法启用
    sqlSession.close();
    System.out.println("\n========新的SQLSession=======\n");
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    UserLoginMapper mapper2 = sqlSession2.getMapper(UserLoginMapper.class);
    List<UserLogin> list2 = mapper2.queryByNameLevel2Cache("核弹");
    for (UserLogin e : list2) {
        System.out.println(e);
    }
}

三次都是查询数据库

测试代码

Github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值