当前位置:首页 > mybatis > 正文内容

源码解析--mybatis一级缓存和二级缓存

关中浪子3年前 (2022-04-07)mybatis1170
买泛域名SSL证书 送5斤装现摘猕猴桃一箱、同时提供技开源商城搭建免费技术支持。
泛域名ssl证书 239元1年送1个月、单域名39元1年,Sectigo(原Comodo证书)全球可信证书,强大的兼容性,高度安全性,如有问题7天内可退、可开发票
加微信VX 18718058521 备注SSL证书
【腾讯云】2核2G4M云服务器新老同享99元/年,续费同价

MyBatis缓存

使用缓存可以时应用更快地获取数据,避免频繁地数据库交互,尤其是在查询越多、缓存命中率越高地情况下,使用缓存地作用就越明显。MyBatis作为持久化框架,提供了非常强大地查询缓存特性,可以非常方便地配置和定制使用。


MyBatis提供了一级缓存、二级缓存两种缓存方式,一般提到MyBatis缓存的时候,都是指二级缓存。一级缓存(也叫本地缓存)默认会开启,并且不能控制,因此很少会提到。


一级缓存主要是sqlSession级别的缓存,在操作数据库时是需要构造sqlSession会话对象的,对同一个对象中的数据可以使用到缓存,不同的sqlSession之间的缓存是不同享的。

二级缓存主要是Mapper级别的缓存(namespace),多个sqlSession是共享缓存的。



一级缓存

一级缓存是sqlSession级别的缓存,作用于在同一个sqlSession当中,在同一个sqlSession中连续执行同一个SQL语句,第一次执行的结果会放入缓存当中,第二次进行查询操作时就可以直接访问缓存结果。


MyBatis是默认支持一级缓存的,不需要做相关配置。

下面对一级缓存进行测试。测试场景如下:

case1:同一个sqlSession下连续的执行同一个查询操作;

case2:在同一个sqlSession下,先进行查询操作,再进行变更操作,然后接着进行同一个SQL查询;

case3:不同的sqlsession下,进行同一个SQL查询操作;

测试环境与前面博客中的测试环境相同。

case1验证:


测试代码:

//case1:同一个sqlSession下连续的执行同一个查询操作

@Test
    public void getStudentByID() throws IOException {
        SqlSession sqlSession = this.getSqlSessionFactory().openSession();
        //通过接口产生代理类
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次执行查询操作,查询id =1
        User user = mapper.queryUserById(1);
        System.out.println(user);
        //第二次查询
        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
    }


日志打印:

image.png


结果:在同一个sqlSession下,连续的查询同一个SQL语句,只查询一次数据库,第二次查询即访问缓存拿到结果。

case2验证:


测试代码:

//case2:在同一个sqlSession下,先进行查询,在变更操作,然后进行同一个SQL查询

@Test
    public void getStudentByID2() throws IOException {
        SqlSession sqlSession = this.getSqlSessionFactory().openSession();
        //通过接口产生代理类
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次执行查询操作,查询id =1
        User user = mapper.queryUserById(1);
        System.out.println(user);
        //进行更新SID=1的学生信息
        User user2 = new User();
        user2.setId(1);
        user2.setUsername("大刀王五");
        mapper.updateUserById(user2);
        sqlSession.commit();
        //第二次查询
        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
    }

日志打印:

image.png


结果:在第一次查询之后,会将数据存入缓存中,当进行了变更操作时,将缓存清空,第二次执行同一个SQL的查询操作时,此时缓中不存在数据,就会继续查询数据库。

case3验证:


测试代码:

//case3:不同的sqlsession下,同一个SQL查询操作

@Test
public void getStudentByID3() throws IOException {
        //创建SQLSession实例1
        SqlSession sqlSession1 = this.getSqlSessionFactory().openSession();
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);

        //创建SQLSession实例2
        SqlSession sqlSession2 = this.getSqlSessionFactory().openSession();
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

        //第一次执行查询操作
        User user = mapper1.queryUserById(1);
        System.out.println(user);

        //第二次查询
        User user1 = mapper2.queryUserById(1);
        System.out.println(user1);
    }

日志打印:

image.png


结果:在同一个sqlsession实例下,连续查询操作可以使用缓存,在不同的sqlSession实例下,缓存是不共享的。

二级缓存

二级缓存是Mapper级别的缓存,默认情况下二级缓存是关闭的。同一个Mapper下不同的sqlSession是可以共享二级缓存的,不同Mapper的缓存是相互隔离的。


二级缓存和一级缓存的区别:


二级缓存范围更大,多个sqlSession可以共享一个Mapper的二级缓存区域。

二级缓存使用步骤:


1、需要在全局配置文件中打开二级缓存的开关。

<settings>
    <!--开启二级缓存的开关-->
    <setting name="cacheEnabled" value="true"/>
</settings>


2、将映射的pojo类实现序列化。

public class Student implements Serializable 

3、在mapper配置文件中添加cache标签。

<mapper namespace="com.tulun.dao.StudentMapper">
    <!--开启本Mapper的二级缓存-->
    <cache></cache>
</mapper>


cache的参数说明:

<mapper namespace="com.tulun.dao.StudentMapper">
    <!--
        cache参数说明:
        flushInterval(缓存刷新间隔)单位毫秒,默认情况下不设置,在变更操作时,进行缓存刷新
        size:(引用次数)记录缓存对象的数量 默认值1024
        readOnly(只读)参数为true:false  表示缓存的数据对象实例是不能被修改
        eviction:缓存失效的策略  默认LRU
        LRU:最近最少使用的:移除最长时间不使用的
        FIFO:先进先出,按照缓存对象的顺序来淘汰
        SOFT:软引用
        WEAK:弱引用
    -->
    <cache eviction="LRU" readOnly="true" flushInterval="10000" size="12"></cache>
</mapper>


cache的配置禁用:

在statement上配置useCache=“false”,禁用的是二级缓存。

<select id="queryUserById" useCache="false" parameterType="int" resultType="com.test.mybatis.User">
        select id,username,birthday,sex,address from user where id = #{id}
    </select>


二级缓存的测试:


case:在同一个Mapper实例下,不同的sqlSession执行同一个查询操作。


测试代码:


//case:在同一个Mapper实例下,不同的sqlsession下,同一个SQL查询操作

 @Test
    public void getStudentByID3() throws IOException {
        //创建SQLSession实例1
        SqlSession sqlSession1 = this.getSqlSessionFactory().openSession();
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);

        //创建SQLSession实例2
        SqlSession sqlSession2 = this.getSqlSessionFactory().openSession();
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);

        //第一次执行查询操作
        User user = mapper1.queryUserById(1);
        System.out.println(user);

        //第二次查询
        User user1 = mapper2.queryUserById(1);
        System.out.println(user1);
    }


日志打印:


结果:在同一个Mapper下,不同的sqlSession实例可以共享缓存。


源码解读:

DefaultSqlSession类实现了SqlSession,所有的增删改查都会走到这个类里面,但是只有select会有缓存,我们找一个selectlist方法进去,会看到这里用到了executor执行器,并且调用query方法

image.png

点进去Executor这个接口,会看到这里有2个实现类,一个是baseExecutor,和cacheExecutor,当我们配置缓存开启时会走cacheExecutor,否则就会走baseExecutor,

其中baseExecutor是一级缓存类,cacheExecutor是二级缓存类

<setting name="cacheEnabled" value="true"/>

image.png


先看baseExecutor一级缓存,这里的this.localCache.getObject(key),就是从缓存里面读取数据,这里第一次肯定是没有的NULL,

再看list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); 这行代码直接从数据库查,我们看this.queryFromDatabase方法

image.png



注意this.localCache.putObject(key, list);就会把查到数据put到缓存中,如果下次还是本SqlSession的同一条sql进来查询会直接从上面的this.localCache.getObject(key)中获取数据

image.png



说完一级缓存再说二级缓存,当二级缓存配置为true时,会进入到CachingExecuor类,如图所示

ms.isUseCache==true时,执行 this.tcm.getObject(Cache,key) 获取缓存,如果没有缓存再进入到 BaseExecutor类里面,获取数据并且缓存到一级缓存里面,

然后会再放入到二级缓存里面,因此这里缓存的顺序应该是 二级缓存---》一级缓存--》数据库

image.png



总结,mybatis二级缓存需要开启并且需要再mapper里面设置 useCache=true 和 <cache></cache>标签,并且不同session之间的二级缓存是可以共享的

二一级缓存默认就是开启的,无法控制,只有当同一个session时缓存才会起作用。

这次就分享到这里,需要有不同意见的人可以留言,并且关注我的公众号 【码农翻生】

找梯子最重要的就是稳定,这个已经上线三年了,一直稳定没有被封过,赶紧下载备用吧!

扫描二维码推送至手机访问。

版权声明:本文由码农翻生发布,如需转载请注明出处。

本文链接:https://lubojian.cn/post/162.html

分享给朋友:

相关文章

超全MyBatis动态代理详解!(绝对干货)绝命三连问

超全MyBatis动态代理详解!(绝对干货)绝命三连问

前言假如有人问你这么几个问题,看能不能答上来Mybatis Mapper 接口没有实现类,怎么实现的动态代理JDK 动态代理为什么不能对类进行代理(充话费送的问题)抽象类可不可以进行 JDK 动态代理(附加问题)答不上来的铁汁,证明 Pro...

mybatis获取sqlsession原理流程以及作用

mybatis获取sqlsession原理流程以及作用

package com.kuang.utils;   import org.apache.ibatis.io.Resources; import org.apache.ibatis.sessio...

源码解析--mybatis mapper接口为啥没有实现类

源码解析--mybatis mapper接口为啥没有实现类

mybatis接口没有实现类,是如何实现功能的呢?程序员都知道是mapper里面的接口名称和mapper.xml里面的名称一致,然后用的时候直接调用接口的方法就好了,但是里面具体的技术实现细节很多码农不一定懂,今天我就分析一下带领大家分析下...

Mybatis MapperScannerConfigurer 自动扫描 将Mapper接口生成代理注入到Spring (mapper如何生成bean)

Mybatis MapperScannerConfigurer 自动扫描 将Mapper接口生成代理注入到SpringMybatis在与Spring集成的时候可以配置MapperFactoryBean来生成Mapper接口的代理. 例如&l...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。