MyBatis缓存机制

1、缓存机制简介

  • MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存,即一级缓存和二级缓存
    • 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
    • 为了提高扩展性,MyBatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

2、一级缓存

  • 一级缓存(local cache),即本地缓存,作用域默认为sqlSession。当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空。

  • 本地缓存不能被关闭,但可以调用 clearCache() 来清空本地缓存,或者改变缓存的作用域。

  • 在mybatis3.1之后,可以配置本地缓存的作用域,即在mybatis.xml中配置。

  • 一级缓存失效有四种情况:

    • 不同的SqlSession对应不同的一级缓存。
    • 同一个SqlSession但是查询条件不同。
    • 同一个SqlSession两次查询期间执行了任何一次增删改操作。
    • 同一个SqlSession两次查询期间手动清空了缓存。
  • 现在根据https://perfectcode.top/2020/12/24/%E6%90%AD%E5%BB%BAMyBatis/搭建一个MyBatis项目,并新建测试类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public class MybatisTest {
    public static void main(String[] args) throws IOException {
    //1.根据全局配置文件创建一个sqlSessionFactory
    //SqlSessionFactory是SqlSession工厂,负责创建SqlSession对象
    //SqlSession:sql会话(代表和数据库的一次会话)
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    //2.获取和数据库的一次会话:getConnection()
    SqlSession sqlSession = sqlSessionFactory.openSession();

    //3.使用SqlSession操作数据库,先获取到dao接口实现
    UserDao mapper = sqlSession.getMapper(UserDao.class);
    //第一次查询
    List<User> allUsers = mapper.getAllUsers();
    System.out.println(allUsers);
    System.out.println("*******************");

    //可调用sqlSession.clearCache()来手动清空缓存
    //在此sqlSession进行第二次相同查询
    List<User> allUsers1 = mapper.getAllUsers();
    System.out.println(allUsers1);
    System.out.println(allUsers == allUsers1);//true
    }
    }

    执行结果如下,可见第二次查询是从一级缓存中获取的:

3、二级缓存

  • 二级缓存(second level cache),全局作用域缓存。

  • 二级缓存默认不开启,需要手动配置。

  • MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口。

  • 二级缓存在 SqlSession 关闭或提交之后才会生效。

  • 使用步骤:

    • ①全局配置文件中开启二级缓存()。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
      <!--resourse:引用类路径资源-->
      <!--url:网络或磁盘资源-->
      <properties resource="jdbc.properties"></properties>

      <settings>
      <!--开启驼峰命名-->
      <setting name="mapUnderscoreToCamelCase" value="true"/>
      <!--开启二级缓存-->
      <setting name="cacheEnabled" value="true"/>
      </settings>

      <environments default="development">
      <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
      <property name="driver" value="${jdbc.driver}"/>
      <property name="url" value="${jdbc.url}"/>
      <property name="username" value="${jdbc.username}"/>
      <property name="password" value="${jdbc.password}"/>
      </dataSource>
      </environment>
      </environments>

      <mappers>
      <!--resource表示从类路径下找资源-->
      <mapper resource="mapper/UserMapper.xml"/>
      </mappers>
      </configuration>
    • ②需要使用二级缓存的映射文件处使用cache配置缓存

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <?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.example.dao.UserDao">

      <!--开启二级缓存-->
      <cache></cache>

      <select id="getAllUsers" resultType="com.example.bean.User" >
      SELECT * FROM t_users;
      </select>
      </mapper>
    • ③注意:POJO需要实现Serializable接口。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      public class User implements Serializable {
      private Integer id ;
      private String userName ;
      private String password ;

      public Integer getId() {
      return id;
      }

      public void setId(Integer id) {
      this.id = id;
      }

      public String getUserName() {
      return userName;
      }

      public void setUserName(String userName) {
      this.userName = userName;
      }

      public String getPassword() {
      return password;
      }

      public void setPassword(String password) {
      this.password = password;
      }

      @Override
      public String toString() {
      return "User{" +
      "id=" + id +
      ", userName='" + userName + '\'' +
      ", password='" + password + '\'' +
      '}';
      }
      }
    • ④新建测试类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      public class MybatisTest {
      public static void main(String[] args) throws IOException {
      String resource = "mybatis-config.xml";
      InputStream inputStream = Resources.getResourceAsStream(resource);
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

      SqlSession sqlSession1 = sqlSessionFactory.openSession();
      SqlSession sqlSession2 = sqlSessionFactory.openSession();

      UserDao mapper1 = sqlSession1.getMapper(UserDao.class);
      UserDao mapper2 = sqlSession2.getMapper(UserDao.class);

      List<User> allUsers = mapper1.getAllUsers();
      System.out.println(allUsers);
      sqlSession1.close();

      List<User> allUsers1 = mapper2.getAllUsers();
      System.out.println(allUsers1);
      sqlSession2.close();
      }
      }

      测试结果:

      其中,命中率0.0表示第一次去缓存中找不到数据,即0/1=0.0;命中率0.5表示第二次去缓存中找到数据,即1/2=0.5。

4、缓存的查询顺序

  • 当MyBatis同时开启一级缓存和二级缓存时:

    • 二级缓存中的数据是一级缓存中关闭就有了,且不会出现一级缓存和二级缓存中有相同数据的情况。
    • 当查询数据时,任何时候都是先查二级缓存,如果其中没有数据,再去查一级缓存,如果一级缓存也没有就去查询数据库,数据库查询结果放在一级缓存。
  • ①为了测试需要,在前面的基础上,在UserDao和UserMapper.xml中新增不同的查询接口方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?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.example.dao.UserDao">

    <!--开启二级缓存-->
    <cache></cache>

    <select id="getAllUsers" resultType="com.example.bean.User" >
    SELECT * FROM t_users;
    </select>

    <select id="getUserById" resultType="com.example.bean.User" >
    SELECT * FROM t_users where id = #{id};
    </select>
    </mapper>
    1
    2
    3
    4
    5
    public interface UserDao {
    List<User> getAllUsers();

    User getUserById(int id);
    }
  • ②新建测试类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class MybatisTest {
    public static void main(String[] args) throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    UserDao mapper1 = sqlSession1.getMapper(UserDao.class);
    List<User> allUsers1 = mapper1.getAllUsers();
    System.out.println(allUsers1);
    sqlSession1.close();

    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    UserDao mapper2 = sqlSession2.getMapper(UserDao.class);
    //从二级缓存中拿数据
    List<User> allUsers2 = mapper2.getAllUsers();
    System.out.println(allUsers2);
    List<User> allUsers3 = mapper2.getAllUsers();
    System.out.println(allUsers3);

    System.out.println("******************");
    //此时二级缓存和一级缓存都没有此数据,就会去查询数据库并把数据放在一级缓存
    User user1 = mapper2.getUserById(1);
    System.out.println(user1);
    //此时先去二级缓存查看数据,发现没有user2的信息,就去一级缓存拿数据
    User user2 = mapper2.getUserById(1);
    System.out.println(user2);

    sqlSession2.close();
    }
    }

    测试结果:

    其中,命中率0.0表示第一次去缓存中找不到数据,即0/1=0.0;命中率0.5(第一个0.5)表示第二次去二级缓存中找到数据(第一次找到数据),即1/2=0.5;命中率0.6666666表示第三次去二级缓存中找到数据(第二次找到数据),即2/3=0.6666666;命中率0.5(第二个0.5)表示第四次去缓存中找不到数据,即2/4=0.5;命中率0.4表示第五次去二级缓存中找不到数据,即2/5=0.4。