0
点赞
收藏
分享

微信扫一扫

mybatis一级缓存,二级缓存探讨上篇

陆佃 2021-09-28 阅读 84
日记本

mybatis中的缓存

通常情况下mybatis会访问数据库获取数据,中间涉及到网络通信,数据库从磁盘中读取数据,然后将数据返回给mybatis,总的来说耗时还是挺长的,mybatis为了加快数据查询的速度,在其内部引入了缓存来加快数据的查询速度。

mybatis中分为一级缓存和二级缓存。

一级缓存是SqlSession级别的缓存,在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。

二级缓存是mapper级别的缓存,多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。

下面我们详细说一下一级缓存和二级缓存的各种用法和注意点。

一级缓存

<font color=#FF0000>一级缓存是SqlSession级别的缓存,每个SqlSession都有自己单独的一级缓存,多个SqlSession之间的一级缓存是相互隔离的,互不影响,mybatis中一级缓存是默认自动开启的。

一级缓存工作原理:在同一个SqlSession中去多次去执行同样的查询,每次执行的时候会先到一级缓存中查找,如果缓存中有就直接返回,如果一级缓存中没有相关数据,mybatis就会去db中进行查找,然后将查找到的数据放入一级缓存中,第二次执行同样的查询的时候,会发现缓存中已经存在了,会直接返回。一级缓存的存储介质是内存,是用一个HashMap来存储数据的,所以访问速度是非常快的。</font>

一级缓存案例

sql脚本如下

DROP DATABASE IF EXISTS `javacode2018`;
CREATE DATABASE `javacode2018`;

USE `javacode2018`;

DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(
  id int AUTO_INCREMENT PRIMARY KEY COMMENT '用户id',
  name VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用户名',
  age SMALLINT NOT NULL DEFAULT 1 COMMENT '年龄'
) COMMENT '用户表';
INSERT INTO t_user VALUES (1,'路人甲Java',30),(2,'张学友',50),(3,'刘德华',50);

下面是查询用户信息,返回一个list

<select id="getList1" resultType="com.javacode2018.chat05.demo9.model.UserModel" parameterType="map">
    SELECT id,name,age FROM t_user
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
    </where>
</select>

对应的mapper接口方法

List<UserModel> getList1(Map<String, Object> paramMap);
com.javacode2018.chat05.demo9.Demo9Test#level1CacheTest1

/**
 * 一级缓存测试
 *
 * @throws IOException
 */
@Test
public void level1CacheTest1() throws IOException {
    String mybatisConfig = "demo9/mybatis-config.xml";
    this.before(mybatisConfig);
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次查询
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
        //第二次查询
        List<UserModel> userModelList2 = mapper.getList1(null);
        log.info("{}", userModelList2);
        log.info("{}", userModelList1 == userModelList2);
    }
}

上面的代码在同一个SqlSession中去执行了2次获取用户列表信息,2次查询结果分别放在userModelList1和userModelList2,最终代码中也会判断这两个集合是否相等,下面我们运行一下看看会访问几次db?

输出结果

01:15.312 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
01:15.340 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
01:15.364 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 3
01:15.364 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=张学友, age=50), UserModel(id=3, name=刘德华, age=50)]
01:15.367 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=张学友, age=50), UserModel(id=3, name=刘德华, age=50)]
01:15.367 [main] INFO  c.j.chat05.demo9.Demo9Test - true

从输出中可以看出看到,sql只输出了一次,说明第一次会访问数据库,第二次直接从缓存中获取的,最后输出了一个true,也说明两次返回结果是同一个对象,第二次直接从缓存中获取数据的,加快了查询的速度。

清空一级缓存的3种方式

同一个SqlSession中查询同样的数据,mybatis默认会从一级缓存中获取,如果缓存中没有,才会访问db,那么我们如何去情况一级缓存呢,强制让查询去访问db呢?

让一级缓存失效有3种方式:

  • SqlSession中执行增、删、改操作,此时sqlsession会自动清理其内部的一级缓存
  • 调用SqlSession中的clearCache方法清理其内部的一级缓存
  • 设置Mapper xml中select元素的flushCache属性值为true,那么执
    行查询的时候会先清空一级缓存中的所有数据,然后去db中获取数据

上面方式任何一种都会让当前SqlSession中的以及缓存失效,进而去db中获取数据,下面我们来分别演示这3种情况。

方式1:增删改让一级缓存失效

当执行增删改操时,mybatis会将当前SqlSession一级缓存中的所有数据都清除。

案例代码:

com.javacode2018.chat05.demo9.Demo9Test#level1CacheTest2

/**
 * 增删改使一级缓存失效
 *
 * @throws IOException
 */
@Test
public void level1CacheTest2() throws IOException {
    String mybatisConfig = "demo9/mybatis-config.xml";
    this.before(mybatisConfig);
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //第一次查询
        List<UserModel> userModelList1 = mapper.getList1(null);
        log.info("{}", userModelList1);
        //新增一条数据
        mapper.insert1(UserModel.builder().id(100).name("路人").age(30).build());
        //第二次查询
        List<UserModel> userModelList2 = mapper.getList1(null);
        log.info("{}", userModelList2);
        log.info("{}", userModelList1 == userModelList2);
    }
}

上面同一个SqlSession中执行了3个操作,同样的查询执行了2次,2次查询中间夹了一个插入操作。

运行输出

21:55.097 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
21:55.135 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
21:55.159 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 3
21:55.159 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=张学友, age=50), UserModel(id=3, name=刘德华, age=50)]
21:55.161 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==>  Preparing: INSERT INTO t_user (id,name,age) VALUES (?,?,?) 
21:55.162 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - ==> Parameters: 100(Integer), 路人(String), 30(Integer)
21:55.165 [main] DEBUG c.j.c.d.mapper.UserMapper.insert1 - <==    Updates: 1
21:55.166 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==>  Preparing: SELECT id,name,age FROM t_user 
21:55.166 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - ==> Parameters: 
21:55.167 [main] DEBUG c.j.c.d.mapper.UserMapper.getList1 - <==      Total: 4
21:55.168 [main] INFO  c.j.chat05.demo9.Demo9Test - [UserModel(id=1, name=路人甲Java, age=30), UserModel(id=2, name=张学友, age=50), UserModel(id=3, name=刘德华, age=50), UserModel(id=100, name=路人, age=30)]
21:55.168 [main] INFO  c.j.chat05.demo9.Demo9Test - false

从输出中可以看出2次查询都访问了db,并且两次查询的结果是不一样的,两个集合也不相等,插入数据让缓存失效是可以理解的,插入操作可能会改变数据库中的数据,所以如果再从缓存中去获取,可能获取到的数据和db中的数据不一致的情况,mybatis为了避免这种情况,在执行插入操作的时候,会将SqlSession中的一级缓存清空。当然删除和修改也同样会改变db中的数据,如果在同一个SqlSession中去执行删除或者修改数据的时候,mybatis也一样会清除一级缓存中的所有数据,删除和修改大家自己可以写2个例子试试,看看是否也会清理一级缓存中的数据。

方式2:SqlSession.clearCache清理一级缓存

SqlSession.clearCache()方法会将当前SqlSession一级缓存中的所有数据清除。
案例略。。。

方式3:Select元素的flushCache置为true

将Mapper xml中select元素的flushCache属性置为true的时候,每次执行这个select元素对应的查询之前,mybatis会将当前SqlSession中一级缓存中的所有数据都清除。

案例代码
新增一个select元素的查询,将flushCache元素置为true,注意:select元素这个属性的默认值是false。

<select id="getList2" flushCache="true" resultType="com.javacode2018.chat05.demo9.model.UserModel" parameterType="map">
    SELECT id,name,age FROM t_user
    <where>
        <if test="id!=null">
            AND id = #{id}
        </if>
        <if test="name!=null and name.toString()!=''">
            AND name = #{name}
        </if>
        <if test="age!=null">
            AND age = #{age}
        </if>
    </where>
</select>

对应测试用例

com.javacode2018.chat05.demo9.Demo9Test#level1CacheTest4

/**
 * 将Mapper xml中select元素的flushCache属性置为true的时候,每次执行这个select元素对应的查询之前,mybatis会将当前SqlSession中一级缓存中的所有数据都清除。
 *
 * @throws IOException
 */
@Test
public void level1CacheTest4() throws IOException {
    String mybatisConfig = "demo9/mybatis-config.xml";
    this.before(mybatisConfig);
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //查询1:getList1
        log.info("查询1 start");
        log.info("查询1:getList1->{}", mapper.getList1(null));
        //查询2:getList1
        log.info("查询2 start");
        log.info("查询2:getList1->{}", mapper.getList1(null));
        //查询3:getList2
        log.info("查询3 start");
        log.info("查询3:getList2->{}", mapper.getList2(null));
        //查询4:getList2
        log.info("查询4 start");
        log.info("查询4:getList2->{}", mapper.getList2(null));
        //查询5:getList1
        log.info("查询5 start");
        log.info("查询5:getList1->{}", mapper.getList1(null));
    }
}

注意上面的代码,代码中有5次查询,第1次、第2次、第5次查询调用的都是getList1,这个查询对应的mapper xml中的select元素的flushCache属性没有设置,默认是false;而第3次和第4次查询调用的是getList2,getList2这个查询对应的mapper xml中的select(id=getList2),它的flushCache属性设置的是true,说明第3和第4次查询会清空当前一级缓存中所有数据。

最终效果应该是查询1访问db拿去数据,然后将其丢到一级缓存中,查询2会直接从一级缓存中拿到数据,而查询3走的是getList2,发现flushCache为true,会先清空一级缓存中所有数据,也就是此时查询1放入缓存的数据会被清理掉,然后查询3会访问db获取数据,然后丢到缓存中;而查询4走的是getList2,发现flushCache为true,会先清空缓存,所以3放入一级缓存的数据会被清空,然后导致查询4也会访问db,查询5去一级缓存中查询数据,因为查询1和2放入缓存的数据都被查询3清空了,所以导致查询5发现一级缓存中没有数据,也会访问db去获取数据。

我们来运行一下看看看看是否和我们分析的一致。

一级缓存使用总结
一级缓存是SqlSession级别的,每个人SqlSession有自己的一级缓存,不同的SqlSession之间一级缓存是相互隔离的

mybatis中一级缓存默认是自动开启的

当在同一个SqlSession中执行同样的查询的时候,会先从一级缓存中查找,如果找到了直接返回,如果没有找到会去访问db,然后将db返回的数据丢到一级缓存中,下次查询的时候直接从缓存中获取

一级缓存清空的3种方式(1:SqlSession中执行增删改会使一级缓存失效;2:调用SqlSession.clearCache方法会使一级缓存失效;3:Mapper xml中的select元素的flushCache属性置为true,那么执行这个查询会使一级缓存失效)

举报

相关推荐

0 条评论