0%

Mybatis的缓存

  • Mybatis的缓存:
    • 一级缓存
    • 二级缓存
    • Mybatis集成EhCache

十四、Mybatis的缓存

  • 缓存 cache:提前把数据放到缓存中(内存中),下一次用的时候,直接从缓存中拿,效率高。

    缓存的作用:通过减少 IO 的方式,来提高程序的执行效率。

  • mybatis 的缓存机制:执行 DQL(select 语句)的时候,将查询结果放到缓存(内存)当中,如果下一次还是这条 select 语句的话,直接从缓存中取,不再查数据库。一方面是减少了IO;另一方面不再执行繁琐的查找算法,效率大大提升。

    另外,当数据库数据发生变化时,mybatis 的缓存会被清空,不存在缓存中存有 “过时数据” 的情况。

    ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-SSM-notebook/img/Mybatis/mybatis缓存.png)

    • mybatis 缓存包括:
      • 一级缓存:将查询到的数据存储到 SqlSession 中。
      • 二级缓存:将查询到的数据存储到 SqlSessionFactory 中。
      • 或者集成其它第三方的缓存:比如 EhCache【Java语言开发的】、Memcache【C语言开发的】等。

14.1 一级缓存

  • 一级缓存的范围是 SqlSession

  • 一级缓存默认是开启的,不需要做任何配置。

    只要使用同一个 SqlSession 对象执行同一条 SQL 语句,就会走缓存。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.f.mybatis.mapper;

    import com.f.mybatis.pojo.Car;

    /**
    * @author fzy
    * @date 2024/1/13 14:20
    */
    public interface CarMapper {
    /**
    * 根据汽车id查询汽车信息
    *
    * @param id
    * @return
    */
    Car selectById(Long id);
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?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.f.mybatis.mapper.CarMapper">
    <select id="selectById" resultType="Car">
    SELECT *
    FROM t_car
    <where>
    id = #{id}
    </where>
    </select>
    </mapper>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Test
    public void testSelectById() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    // 第一次查询
    Car car1 = mapper.selectById(9L);
    System.out.println(car1);
    // 第二次查询
    Car car2 = mapper.selectById(9L);
    System.out.println(car2);
    SqlSessionUtil.close(sqlSession);
    }
    • 根据 logback 日志可以看到,虽然查询了两次,但由于是同一个 SqlSession 对象,并且执行的是同一条 SQL 语句,所以只走了一次 SQL 语句查询,后面走的就是缓存了。

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-SSM-notebook/img/Mybatis/一级缓存.png)

  • 什么情况下不走缓存?

    • 第一种:SqlSession 对象不是同一个,就不走缓存。
    • 第二种:查询条件变化了。
  • 什么时候一级缓存失效?

    一级缓存失效情况包括两种:

    • 第一种:第一次查询和第二次查询之间,手动清空了一级缓存。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      @Test
      public void testSelectById() {
      SqlSession sqlSession = SqlSessionUtil.openSession();
      CarMapper mapper = sqlSession.getMapper(CarMapper.class);
      // 第一次查询
      Car car1 = mapper.selectById(9L);
      System.out.println(car1);
      // 手动清空一级缓存
      sqlSession.clearCache();
      // 第二次查询
      Car car2 = mapper.selectById(9L);
      System.out.println(car2);
      SqlSessionUtil.close(sqlSession);
      }
      1
      2
      // 手动清空一级缓存
      sqlSession.clearCache();
      • 根据 logback 日志可以看到,走了两次 SQL 语句查询。

        ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-SSM-notebook/img/Mybatis/清空一级缓存.png)

    • 第二种:第一次查询和第二次查询之间,执行了增删改操作【和增删改哪张表没有关系,只要有insert delete update操作,一级缓存就失效】。

14.2 二级缓存

  • 二级缓存的范围是 SqlSessionFactory

    使用二级缓存需要具备以下几个条件:

    1. <setting name="cacheEnabled" value="true">

      全局性地开启或关闭所有映射器配置文件中已配置的任何缓存,默认就是true,无需设置。

    2. 在需要使用二级缓存的 SqlMapper.xml 文件中添加配置:<cache/>

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <!--CarMapper.xml文件-->
      <?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.f.mybatis.mapper.CarMapper">
      <!--
      在对应的SqlMapper.xml文件中添加下面的标签<cache/>,
      来表示要使用二级缓存
      -->
      <cache/>
      </mapper>
    3. 使用二级缓存的实体类对象必须是可序列化的,也就是必须实现 java.io.Serializable 接口。

      1
      2
      3
      4
      5
      6
      /**
      * 封装汽车相关信息的pojo类。普通的的java类。
      */
      public class Car implements Serializable {
      ......
      }
    4. SqlSession 对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中,此时二级缓存才可用。

  • 使用二级缓存:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.f.mybatis.mapper;

    import com.f.mybatis.pojo.Car;

    /**
    * @author fzy
    * @date 2024/1/13 14:20
    */
    public interface CarMapper {
    /**
    * 根据汽车id查询汽车信息
    *
    * @param id
    * @return
    */
    Car selectById2(Long id);
    }
    1
    2
    3
    4
    5
    6
    /**
    * 封装汽车相关信息的pojo类。普通的的java类。
    */
    public class Car implements Serializable {
    ......
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <?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.f.mybatis.mapper.CarMapper">
    <!--
    在对应的SqlMapper.xml文件中添加下面的标签<cache/>,
    来表示要使用二级缓存
    -->
    <cache/>
    <select id="selectById2" resultType="Car">
    SELECT *
    FROM t_car
    <where>
    id = #{id}
    </where>
    </select>
    </mapper>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @Test
    public void testSelectById2() throws Exception {
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
    CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
    // 下面这行代码执行结束之后,数据缓存到了一级缓存中(sqlSession1中)
    // 如果还没有关闭sqlSession1对象,二级缓存中还是没有数据的
    Car car1 = mapper1.selectById2(8L);
    // 下面这行代码执行结束之后,数据缓存到了一级缓存中(sqlSession2中)
    Car car2 = mapper2.selectById2(8L);
    // sqlSession1和sqlSession2分别关闭后,它们的一级缓存中的数据会被保存到二级缓存中
    sqlSession1.close();
    sqlSession2.close();
    }
    • 上面的单元测试还没有使用到二级缓存,如果将 sqlSession1.close(); 改变位置,就用到了二级缓存:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @Test
      public void testSelectById2() throws Exception {
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
      SqlSession sqlSession1 = sqlSessionFactory.openSession();
      SqlSession sqlSession2 = sqlSessionFactory.openSession();
      CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
      CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
      Car car1 = mapper1.selectById2(8L);
      // 在这里关闭sqlSession1,sqlSession1一级缓存中的数据会保存到二级缓存中
      sqlSession1.close();
      Car car2 = mapper2.selectById2(8L);
      sqlSession2.close();
      }
  • 什么时候二级缓存失效?

    • 只要两次查询之间出现了增删改操作,二级缓存就会失效【一级缓存也会失效】。

14.3 Mybatis集成EhCache

  • 集成 EhCache 是为了代替 mybatis 自带的二级缓存,一级缓存是无法替代的

    mybatis 对外提供了接口,也可以集成第三方的缓存组件。比如 EhCacheMemcache 等。

    EhCache 是 Java 写的,Memcache 是 C 语言写的。所以 mybatis 集成 EhCache 较为常见,按照以下步骤操作,就可以完成集成:

    • 第一步:引入 mybatis 整合 Ehcache 的依赖。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      ...
      <dependencies>
      ...
      <dependency>
      <groupId>org.mybatis.caches</groupId>
      <artifactId>mybatis-ehcache</artifactId>
      <version>1.2.2</version>
      </dependency>
      </dependencies>
      ...
    • 第二步:在类的根路径下新建 echcache.xml 文件,并提供以下配置信息。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      <?xml version="1.0" encoding="UTF-8"?>
      <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
      updateCheck="false">
      <!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存-->
      <diskStore path="e:/ehcache"/>

      <!--defaultCache:默认的管理策略-->
      <!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
      <!--maxElementsInMemory:在内存中缓存的element的最大数目-->
      <!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
      <!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
      <!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问-->
      <!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问-->
      <!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
      <!--FIFO:first in first out (先进先出)-->
      <!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存-->
      <!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
      <defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
      timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>

      </ehcache>
    • 第三步:修改 SqlMapper.xml 文件中的 <cache/> 标签,添加 type 属性。

      1
      2
      3
      4
      5
      6
      7
      8
      <?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.f.mybatis.mapper.CarMapper">
      <!--集成ehcache组件-->
      <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
      </mapper>
    • 第四步:编写测试程序使用。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      @Test
      public void testSelectById2() throws Exception {
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
      SqlSession sqlSession1 = sqlSessionFactory.openSession();
      SqlSession sqlSession2 = sqlSessionFactory.openSession();
      CarMapper mapper1 = sqlSession1.getMapper(CarMapper.class);
      CarMapper mapper2 = sqlSession2.getMapper(CarMapper.class);
      Car car1 = mapper1.selectById2(8L);
      // 在这里关闭sqlSession1,sqlSession1一级缓存中的数据会保存到二级缓存中
      sqlSession1.close();
      Car car2 = mapper2.selectById2(8L);
      sqlSession2.close();
      }
---------------The End---------------