MyBatis——关于一级缓存 & 二级缓存的案例详解

Posted 张起灵-小哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis——关于一级缓存 & 二级缓存的案例详解相关的知识,希望对你有一定的参考价值。

文章目录:

1.写在前面

2.关于MyBatis中的缓存

3.一级缓存案例详解

3.1 首先写一个实体Bean

3.2 dao接口、对应的mapper映射文件

3.3 mybatis配置文件、工具类

3.4 测试方法

3.4.1 同一个会话对象查询同一个数据

3.4.2 同一个会话对象查询两个不同的数据

3.4.3 insert、update、delete操作会刷新缓存

3.4.4 手动清理缓存

4.二级缓存案例讲解

4.1 开启二级缓存之后,实体类需要实现序列化

4.2 dao接口、mapper文件、myabtis配置文件

4.3 测试方法

4.3.1 一级缓存面对两个SqlSession会话对象

4.3.2 二级缓存面对两个SqlSession会话对象

5.MyBatis缓存原理


1.写在前面

首先,我们来聊聊什么是缓存Cache?

说到数据库啊,无论是mysql、Sql Server、Oracle、Redis等等,它们当中都是有缓存机制的,那么到底什么是缓存呢?

  • 存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(相当于从内存中拷贝了一块高速内存区,存放临时数据)中,用户去查询数据就不用从磁盘上(关系型数据库文件中)查询了,只需要去缓存中查询有无即可,从而提高了查询效率,解决了高并发系统的性能问题。

那么问什么要使用缓存呢?

  • 目的是为了减少与数据库的交互次数,减少系统开销,提高系统的执行效率。

什么样的数据可以使用缓存呢?

  • 经常查询,并且不经常改变的数据。

2.关于MyBatis中的缓存

MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率。

我们也可以在官方文档中看到有关缓存的定义说明

在MyBatis框架中默认定义了两级缓存:一级缓存和二级缓存。

  • 默认情况下,MyBatis只开启了一级缓存(SqlSession级别的缓存,也称为本地缓存)
  • 二级缓存需要手动开启和配置,它是基于namespace级别的缓存。
  • 为了提高扩展性,MyBatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存。

3.一级缓存案例详解

一级缓存也叫本地缓存:SqlSeesion。

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中(也就是同一个SqlSession对象)。
  • 以后如果需要获取相同的数据,就可以直接在缓存中拿,没有必须再去查询数据库了。
  • 一级缓存是默认开启的,只在一次SqlSession会话中有效,也就是从当前SqlSession拿到连接到close关闭连接这个区间段。

3.1 首先写一个实体Bean

头三个注解是lombok中的,用来自动生成get/set方法、toString、无参/有参构造方法、hashCode、equals这些方法。

pom文件中的依赖分别添加了:mybatis、mysql驱动、lombok、junit

package com.szh.mybatis.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 *
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private Integer id;
    private String name;
    private Integer age;
}

3.2 dao接口、对应的mapper映射文件

package com.szh.mybatis.mapper;

import com.szh.mybatis.bean.User;
import org.apache.ibatis.annotations.Param;

/**
 *
 */
public interface UserMapper {

    /**
     * 根据id查询用户信息
     * @param id
     * @return
     */
    User selectUserById(@Param("uid") Integer id);

    /**
     * 修改某个用户的信息
     * @param user
     * @return
     */
    int updateUser(User user);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.szh.mybatis.mapper.UserMapper">
    <!-- 使用insert、update、delete、select标签编写sql语句 -->

    <sql id="userInfo">
        id, name, age
    </sql>
    <sql id="userTable">
        user
    </sql>

    <select id="selectUserById" resultType="com.szh.mybatis.bean.User">
        select <include refid="userInfo"/>
        from <include refid="userTable"/>
        where id=#{uid}
    </select>

    <update id="updateUser" parameterType="com.szh.mybatis.bean.User">
        update <include refid="userTable"/>
        set name=#{name},age=#{age}
        where id=#{id}
    </update>
</mapper>

3.3 mybatis配置文件、工具类

这个可以参考这篇文章,这两个代码是一样的。

https://blog.csdn.net/weixin_43823808/article/details/118111961

3.4 测试方法

3.4.1 同一个会话对象查询同一个数据

    @Test
    public void test01() {
        SqlSession session=MyBatisUtil.getSqlSession();
        UserMapper userMapper=session.getMapper(UserMapper.class);

        User user = userMapper.selectUserById(1001);
        System.out.println(user);
        System.out.println("==============================");
        User user1 = userMapper.selectUserById(1001);
        System.out.println(user);

        session.close();
    }

可以看到我们new第一个对象时,它去数据库中查询了一次,执行了一次select语句;而我们new第二个对象时,这个时候,它就没有再进数据库,没有执行select语句,因为面对同一个SqlSeesion会话对象,第一次查询之后,这条数据就已经存在于缓存中了,所以第二次查询直接去缓存中拿就可以了。

3.4.2 同一个会话对象查询两个不同的数据

    @Test
    public void test02() {
        SqlSession session=MyBatisUtil.getSqlSession();
        UserMapper userMapper=session.getMapper(UserMapper.class);

        User user = userMapper.selectUserById(1001);
        System.out.println(user);
        System.out.println("==============================");
        User user1 = userMapper.selectUserById(1002);
        System.out.println(user);

        session.close();
    }

这个肯定是执行两次select语句了,因为虽然是同一个SqlSession会话对象,但这查询的是两条不同的数据,所以自然会执行两次sql语句,可以理解为缓存中此时有1001、1002这两条数据了。

3.4.3 insert、update、delete操作会刷新缓存

    @Test
    public void test03() {
        SqlSession session=MyBatisUtil.getSqlSession();
        UserMapper userMapper=session.getMapper(UserMapper.class);

        User user = userMapper.selectUserById(1001);
        System.out.println(user);

        int rows=userMapper.updateUser(new User(1002,"小宋",21));

        System.out.println("==============================");
        User user1 = userMapper.selectUserById(1001);
        System.out.println(user);

        session.close();
    }

这里虽然是面对同一个SqlSession会话对象,然后查询的也是同一个数据,但是中间执行了一次update修改操作,所以这时候会刷新缓存,当你第二次再查询1001数据时,缓存已经被刷新了,就没有1001这条数据了,所以两次查询都会去数据库中查找。

3.4.4 手动清理缓存

    @Test
    public void test04() {
        SqlSession session=MyBatisUtil.getSqlSession();
        UserMapper userMapper=session.getMapper(UserMapper.class);

        User user = userMapper.selectUserById(1001);
        System.out.println(user);

        session.clearCache();//手动清理缓存

        System.out.println("==============================");
        User user1 = userMapper.selectUserById(1001);
        System.out.println(user);

        session.close();
    }

这个就不再解释了,因为你都手动刷新缓存了,所以select两次,肯定两次都会去数据库中查找,因为缓存被刷新了。


4.二级缓存案例讲解

二级缓存也叫全局缓存,因为之前一级缓存的作用域太低了,所以就诞生了二级缓存。

二级缓存是基于namespace级别的缓存,一个命名空间(namespace)对应一个二级缓存。

工作机制:

  • 一个会话对象(SqlSession)查询一条数据,这个数据就会被存放在当前会话的一级缓存中。
  • 如果当前会话关闭了,这个会话对应的一级缓存就没了;只有当前会话关闭或者提交了,一级缓存中的数据才会被保存到二级缓存中。那么此时新的会话查询数据,就可以从二级缓存中获取数据。
  • 不同的mapper具有不同的namespace,查出的数据自然也会放在自己对应的缓存中。
  • 只要开启了二级缓存,在同一个mapper下就有效。

4.1 开启二级缓存之后,实体类需要实现序列化

package com.szh.mybatis.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serial;
import java.io.Serializable;

/**
 *
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {

    @Serial
    private static final long serialVersionUID = 8349440997686871823L;
    private Integer id;
    private String name;
    private Integer age;
}

4.2 dao接口、mapper文件、myabtis配置文件

dao接口和上面一级缓存案例的一样。

mapper文件中添加了一个 <cache/> 标签,其他都一样。

<cache/>

mybatis配置文件中多了这一行。

<!-- 显式的开启缓存-->
<setting name="cacheEnabled" value="true"/>

4.3 测试方法

4.3.1 一级缓存面对两个SqlSession会话对象

先把mapper文件中的 <cache/> 标签注释掉。

    @Test
    public void test05() {
        SqlSession session=MyBatisUtil.getSqlSession();
        SqlSession session2=MyBatisUtil.getSqlSession();
        UserMapper userMapper=session.getMapper(UserMapper.class);
        UserMapper userMapper2=session2.getMapper(UserMapper.class);

        User user = userMapper.selectUserById(1001);
        System.out.println(user);
        session.close();

        System.out.println("==============================");
        User user2 = userMapper2.selectUserById(1001);
        System.out.println(user2);
        session2.close();
    }

可以看到,我们在测试方法中创建了两个会话对象(SqlSeesion),那么在第一个会话对象close关闭之后,一级缓存就没了,所以第二个会话对象肯定还会去数据库中查找数据啊。

4.3.2 二级缓存面对两个SqlSession会话对象

先把mapper文件中的 <cache/> 标签打开。测试方法和上面的一样。

我们来看下面的执行结果。

这个时候,我们开启了二级缓存,那就不一样了啊,你第一个会话对象select查询之后,执行close关闭了,此时二级缓存会发挥作用,它会将你一级缓存(对应第一个会话对象)中查询到的那个数据存放到二级缓存中,这个时候,第二个会话对象再执行select,就不用去数据库中查找了,因为这个数据已经存在在二级缓存中了,直接拿数据就可以了。所以自始至终,只用执行一次select语句。

 


5.MyBatis缓存原理

默认开启一级缓存,之后使用<cache/>开启二级缓存。测试。

    @Test
    public void test06() {
        SqlSession session=MyBatisUtil.getSqlSession();
        SqlSession session2=MyBatisUtil.getSqlSession();
        UserMapper userMapper=session.getMapper(UserMapper.class);
        UserMapper userMapper2=session2.getMapper(UserMapper.class);

        //查询1001,此时数据库缓存中什么都没有,查询1001,直接去数据库中查找,执行sql语句
        User user = userMapper.selectUserById(1001);
        System.out.println(user);
        session.close(); //关闭第一个会话对象,此时1001数据会被转移到二级缓存中

        System.out.println("==============================");

        //查询1001,先去二级缓存中查找,有1001,直接从二级缓存中拿,不用再去数据库中查找,自然不会执行sql语句
        User user2=userMapper2.selectUserById(1001);
        System.out.println(user2);

        //查询1002,二级缓存中没有、一级缓存中也没有,所以直接去数据库中查找,执行sql语句
        User user3=userMapper2.selectUserById(1002);
        System.out.println(user2);
        System.out.println(user3);

        //查询1002,先去二级缓存中查找,有1002,直接从二级缓存中拿,不用再去数据库中查找,自然不会执行sql语句
        User user4=userMapper2.selectUserById(1002);
        System.out.println(user4);

        session2.close();
    }

 

以上是关于MyBatis——关于一级缓存 & 二级缓存的案例详解的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis的二级缓存

Mybaits(13)缓存

MyBatis缓存看这一篇就够了(一级缓存+二级缓存+缓存失效+缓存配置+工作模式+测试)

MyBatis学习--查询缓存

关于mybatis中一级缓存和二级缓存的简单介绍

mybatis关于二级缓存的配置及源码分析