前言:

推荐免费MyBatis基础教程视频:【狂神说Java】Mybatis最新完整教程IDEA版通俗易懂_哔哩哔哩_bilibili

缓存简介

查询 连接数据库 耗费资源

缓存就是将查询的数据放入缓存中,减轻服务器的消耗

缓存

什么是缓存

  • 存在内存中的临时数据
  • 用户经常查询的数据放在缓存中,提高查询效率,解决高并发问题

为什么使用缓存

  • 减少和数据的交互次数,减少系统开销,提高效率

什么样的数据能使用缓存

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

MyBatis缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。

MyBatis系统默认定义一级缓存和二级缓存

默认情况下,只启用了本地的会话缓存(一级缓存),它仅仅对一个会话中的数据进行缓存。

要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

1
<cache/>

一级缓存

创建一个新的Maven模块,复制上一个模块的代码,使用User表创建实体类,修改核心配置文件

注意:一级缓存默认开启且无法关闭

测试一级缓存

  1. 开启日志

  2. 编写所需要要接口和xml配置文件

    UserMapper.java

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

    import com.jokerdig.pojo.User;
    import org.apache.ibatis.annotations.Param;

    import java.util.List;

    /**
    * @author Joker大雄
    * @data 2022/5/5 - 12:12
    **/
    public interface UserMapper {
    // 查询用户
    User getUser(@Param("id") int id);

    }

    UserMapper.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <?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.jokerdig.mapper.UserMapper">

    <!-- 查询用户 来测试缓存-->
    <select id="getUser" resultType="user">
    select * from user where id =#{id}
    </select>
    </mapper>
  3. 测试运行

    UserTest.java

    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
    package com.jokerdig.test;

    import com.jokerdig.mapper.UserMapper;
    import com.jokerdig.pojo.User;
    import com.jokerdig.util.MyBatisUtil;
    import org.apache.ibatis.session.SqlSession;
    import org.junit.Test;

    /**
    * @author Joker大雄
    * @data 2022/5/5 - 12:08
    **/
    public class UserTest {
    @Test
    public void test(){
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUser(1);
    System.out.println(user);
    // 在查一遍同样的用户
    System.out.println("============================");
    User user1 = mapper.getUser(1);
    System.out.println(user1);
    sqlSession.close();
    }
    }

    运行结果,观察查询语句执行了几次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ......
    Opening JDBC Connection
    Created connection 1019384604.
    ==> Preparing: select * from user where id =?
    ==> Parameters: 1(Integer)
    <== Columns: id, name, pwd
    <== Row: 1, admin, 123
    <== Total: 1
    User(id=1, name=admin, pwd=123)
    ============================
    User(id=1, name=admin, pwd=123)
    Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3cc2931c]
    Returned connection 1019384604 to pool.

    Process finished with exit code 0

缓存注意事项

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用LRU算法来清除不需要的缓存。
  • 缓存不会定时进行刷新。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
  • 缓存可使用clearCache()手动清除

二级缓存

启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

1
<cache/>

使用@CacheNamespaceRef 注解指定缓存作用域。

二级缓存工作机制

  • 一个会话查询一条数据,数据就被放在一级缓存中,会话关闭(一级缓存)即失效
  • 我们需要的会话关闭了,一级缓存中的数据被保存到二级缓存中(一级缓存失效后,数据被保存到二级缓存)
  • 不同mapper查出的数据会放在自己对应的缓存中
  • 二级缓存基于namespace,一个命名空间对应一个二级缓存

这些属性可以通过 cache 元素的属性来修改。比如:

1
2
3
4
5
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

缓存清除策略

  • LRU – 最近最少使用:移除最长时间不被使用的对象。(默认)
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

测试二级缓存

  1. 开启全局缓存

    mybatis-config.xml

    1
    2
    <!--        开启全局缓存 默认是开启的 这里只是为了更好识别-->
    <setting name="cacheEnabled" value="true"/>
  2. 在xml中开启缓存

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

    也可以添加一些配置

    1
    2
    3
    4
    5
    <cache
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"/>
  3. 测试运行

    UserTest.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Test
    public void test1(){
    // 创建两个不同的sqlsession 来测试
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    SqlSession sqlSession1 = MyBatisUtil.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
    User user = mapper.getUser(1);
    System.out.println(user);
    // 关闭第一个sqlsession 一级缓存关闭 数据被放到二级缓存
    sqlSession.close();
    // 在查一遍同样的用户
    System.out.println("============================");
    User user1 = mapper1.getUser(1);
    System.out.println(user1);
    sqlSession1.close();
    }

    运行结果,发现二级缓存在第一个sqlSession关闭后有效

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Opening JDBC Connection
    Created connection 1183888521.
    ==> Preparing: select * from user where id =?
    ==> Parameters: 1(Integer)
    <== Columns: id, name, pwd
    <== Row: 1, admin, 123
    <== Total: 1
    User(id=1, name=admin, pwd=123)
    Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4690b489]
    Returned connection 1183888521 to pool.
    ============================
    Cache Hit Ratio [com.jokerdig.mapper.UserMapper]: 0.5
    User(id=1, name=admin, pwd=123)

    Process finished with exit code 0

我们在创建实体类时,最好为其实现序列化Serializable

小结:

  • 只要开启了二级缓存,在同一个Mapper下有效
  • 所有的数据都会先放在一级缓存中
  • 只有当会话提交,或者关闭的时候,才会提交二级缓存