Mybatis中的一级缓存和二级缓存

ORM框架一般都会有缓存机制,像Hibernate和Mybatis中都有提供缓存功能用以提升查询的效率和减少数据库的压力。同样的Mybatis和Hibernate一样有一级缓存和二级缓存,并且预留了集成第三方缓存的接口。

一、一级缓存

一级缓存是Mybatis对缓存的默认支持,在没有配置的情况下,Mybatis还是会开启一级缓存,一级缓存是相对于同一个SqlSession而言的。

在参数和SQL语句相同的情况下,如果我们使用的是同一个SqlSession对象去调用一个Mapper方法,那么只会执行一次SQL查询,因为第一次查询后,Mybatis就会将数据放置到缓存中,之后再次查询时,如果没有声明要刷新,那么SqlSession都会取出当前缓存的数据,而不会再次发送SQL到数据库去查询。

在这里插入图片描述

1. 代码示例

下面用代码简单演示一下一级缓存,两次查询都用同一个SqlSession对象,代码如下所示:

@Test
public void userFindByIdTest() {
    // 环境初始化...
    
    // 初始化mybatis,创建SqlSessionFactory类的实例
    SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
    
    // 创建session实例
    SqlSession session = sqlMapper.openSession();

    // 两次查询
    User user = session.selectOne("findById", 1);
    User user2 = session.selectOne("findById", 1);

    System.out.println(user.getUname());
    session.close();
}

运行上面代码,我们可以看到,两次查询最终只打印出一次SQL语句,说明第二次查询是缓存命中的:

在这里插入图片描述

2. 一级缓存的生命周期

  • Mybatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象。Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的 Executor 对象还有PerpetualCache对象也一并释放掉;
  • 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
  • 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;
  • SqlSession中执行了任何一个修改性操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用。

3. 如何判断两次查询是否相同

mybatis认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。

  • 传入的statementId
  • 查询时要求的结果集中的结果范围;
  • 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的Sql语句字符串(boundSql.getSql() );
  • 传递给java.sql.Statement要设置的参数值。

二、二级缓存

Mybatis中的二级缓存是用来解决一级缓存不能跨回话共享的问题的,它的范围是namespace级的,可以被多个SqlSession共享(只要是同一个接口中的相同方法,都可以共享),生命周期与应用同步。

如果应用程序开启了二级缓存,并且Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,Mybatis会从二级缓存中取输入,其次才是一级缓存,即Mybatis查询数据的顺序是:二级缓存→一级缓存→数据库。

作为一个作用范围更广的缓存,它肯定是在SqlSession外层,否则不可能被多个SqlSession共享。而一级缓存是在SqlSession内部的,所以二级缓存肯定是工作在一级缓存之前,也就是只有取不到二级缓存的情况下才到一个会话中去取一级缓存。那么二级缓存存放在哪个对象中维护呢?

在这里插入图片描述

二级缓存要跨会话共享,SqlSession本身和它里面的BaseExecutor已经满足不了需求了,那我们应该在BaseExecutor之外创建一个对象。实际上Mybatis用了一个装饰器的类来维护,就是CachingExecutor。如果启用了二级缓存,Mybatis在创建Executor对象的时候会对Executor进行装饰。CachingExecutor对于查询请求,会判断二级缓存是否有缓存结果,如果有就直接返回,如果没有委派交给真正的查询器Executor实现类,比如SimpleExecutor来进行查询,再走到一级缓存的流程。最后会把结果缓存起来,并且返回给用户。

1. 开启二级缓存

  • 配置 mybatis.configuration.cache-enable=true只要没有显示地设置cacheEnable=false,都会用cachingExecutor装饰基本的执行器。

  • 在Mapper.xml中配置标签:

    <cache type="org.apache.ibatis.cache.impl.PerpetualCache"
           size="1024"
           eviction="LRU"
           flushInterval="120000"
           readOnly="false"/>
    

2. 配置说明

按照上面的配置方式后,二级缓存就会生效,这个xml配置的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

注:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

Mapper.xml 配置了之后,select()会被缓存。update()、delete()、insert()会刷新缓存。:如果cacheEnabled=true,Mapper.xml 没有配置标签,还有二级缓存吗?(没有)还会出现CachingExecutor 包装对象吗?(会)

只要cacheEnabled=true 基本执行器就会被装饰。有没有配置,决定了在启动的时候会不会创建这个mapper的Cache 对象,只是最终会影响到CachingExecutorquery 方法里面的判断。如果某些查询方法对数据的实时性要求很高,不需要二级缓存,怎么办?我们可以在单个Statement ID 上显式关闭二级缓存(默认是true):

<select id="selectBlog" resultMap="BaseResultMap" useCache="false">

文章信息来源:

  • https://www.cnblogs.com/wuzhenzhao/p/11103043.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值