2025/2/24 上午《尚硅谷》—— mybatis的一级缓存以及使一级缓存失效的四种情况

一、mybatis的一级缓存(含项目示例演示)

1、示例目录结构

mybatis_cache/
├── C:/Users/杨智慧/Desktop/SSM/mybatis_cache/
│   ├── .idea/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/
│   │   │   │   ├── com.atguigu.mybatis/
│   │   │   │   │   ├── mapper/
│   │   │   │   │   │   └── CacheMapper.java
│   │   │   │   │   ├── pojo/
│   │   │   │   │   │   └── Emp.java
│   │   │   │   │   └── utils/
│   │   │   │   │       └── SqlSessionUtil.java
│   │   │   ├── resources/
│   │   │   │   ├── com.atguigu.mybatis.mapper/
│   │   │   │   │   └── CacheMapper.xml
│   │   │   │   ├── jdbc.properties
│   │   │   │   ├── log4j.xml
│   │   │   │   └── mybatis-config.xml
│   │   ├── test/
│   │   │   └── java/
│   │   │       └── CacheMapperTest.java
│   ├── target/
│   └── pom.xml
├── 外部库/
└── 临时文件和控制台/

2、实例演示

CacheMapper.java代码

package com.atguigu.mybatis.mapper;

import com.atguigu.mybatis.pojo.Emp;
import org.apache.ibatis.annotations.Param;

public interface CacheMapper {
    /* *
     *
     *根据员工id查询员工信息
     * @param empId
     * @return com.atguigu.mybatis.pojo.Emp
     * @author yzh
     * @create 2025/2/24
     **/
    Emp getEmpById(@Param("empId") Integer empId);
}

CacheMapper.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.atguigu.mybatis.mapper.CacheMapper">


    <!--Emp getEmpById(@Param("empId") Integer empId);-->
    <select id="getEmpById" resultType="Emp">
        select *
        from t_emp where emp_id = #{empId};        

    </select>
</mapper>

CacheMapperTest.java代码

@Test
public void testGetEmpById() {
    SqlSession sqlSession1 = SqlSessionUtil.getSqlSession();
    CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
    Emp emp1 = mapper1.getEmpById(empId: 1);
    System.out.println(emp1);
    Emp emp2 = mapper1.getEmpById(empId: 1);
    System.out.println(emp2);

    SqlSession sqlSession2 = SqlSessionUtil.getSqlSession();
    CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
    Emp emp3 = mapper2.getEmpById(empId: 1);
    System.out.println(emp3);
}

输出结果

DEBUG 02-24 08:35:56,895 ==>  Preparing: select * from t_emp where emp_id = ?; (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:35:56,941 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:35:56,971 <==      Total: 1 (BaseJdbcLogger.java:137) 
Emp{empId=1, empName='张三', age=20, gender='男'}
Emp{empId=1, empName='张三', age=20, gender='男'}
DEBUG 02-24 08:35:57,015 ==>  Preparing: select * from t_emp where emp_id = ?; (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:35:57,016 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:35:57,018 <==      Total: 1 (BaseJdbcLogger.java:137) 
Emp{empId=1, empName='张三', age=20, gender='男'}

进程已结束,退出代码为 0

3、分析总结

MyBatis 的一级缓存:

  • MyBatis 的一级缓存是 SqlSession 级别的缓存,默认开启。

  • 在同一个 SqlSession 中,执行相同的 SQL 查询时,第一次查询的结果会被缓存,后续相同的查询会直接从缓存中获取,而不会再次访问数据库。

  • 一级缓存在 SqlSession 关闭或执行了增删改操作后会被清空。

输出结果分析:

  • 在 CacheMapperTest 中,sqlSession1 第一次查询 empId=1 时,会执行 SQL 查询并将结果缓存。

  • 第二次查询 empId=1 时,直接从缓存中获取结果,不会再次执行 SQL。

  • 使用 sqlSession2 查询 empId=1 时,由于是不同的 SqlSession,会再次执行 SQL 查询。

二、使一级缓存失效的四种情况

MyBatis 的一级缓存是 SqlSession 级别的缓存,默认情况下,同一个 SqlSession 中执行相同的查询会从缓存中获取数据。然而,在某些情况下,一级缓存会失效,导致再次查询时会重新访问数据库。以下是使一级缓存失效的四种情况:


1. 执行了增删改操作(INSERT、UPDATE、DELETE)

当执行了 INSERTUPDATE 或 DELETE 操作时,MyBatis 会清空当前 SqlSession 的一级缓存,以确保缓存中的数据与数据库保持一致。

以INSERT作为示例:

 CacheMapper.java代码

    /* *
     *
     * 添加员工信息
     * @param emp
     * @return void
     * @author yzh
     * @create 2025/2/24
     **/
    void insertEmp(Emp emp);

CacheMapper.xml代码

    <!--Emp getEmpById(@Param("empId") Integer empId);-->
    <select id="getEmpById" resultType="Emp">
        select *
        from t_emp where emp_id = #{empId};
    </select>

    <!--void insertEmp(Emp emp);-->
    <insert id="insertEmp">
        insert into t_emp values (null,#{empName},#{age},#{gender},null)
    </insert>

CacheMapperTest.java代码

    @Test
    public void testGetEmpById() {
        SqlSession sqlSession1 = SqlSessionUtil.getSqlSession();
        CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
        Emp emp1 = mapper1.getEmpById(1);
        System.out.println(emp1);
        mapper1.insertEmp(new Emp( null,  "小红",  25, "男"));
        Emp emp2 = mapper1.getEmpById(1);
        System.out.println(emp2);
    }

输出结果

DEBUG 02-24 08:51:36,369 ==>  Preparing: select * from t_emp where emp_id = ?; (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:51:36,395 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:51:36,415 <==      Total: 1 (BaseJdbcLogger.java:137) 
Emp{empId=1, empName='张三', age=20, gender='男'}
DEBUG 02-24 08:51:36,417 ==>  Preparing: insert into t_emp values (null,?,?,?,null) (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:51:36,418 ==> Parameters: 小红(String), 25(Integer), 男(String) (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:51:36,419 <==    Updates: 1 (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:51:36,419 ==>  Preparing: select * from t_emp where emp_id = ?; (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:51:36,419 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137) 
DEBUG 02-24 08:51:36,420 <==      Total: 1 (BaseJdbcLogger.java:137) 
Emp{empId=1, empName='张三', age=20, gender='男'}

进程已结束,退出代码为 0

分析:

  • 第一次查询 empId=1 时,会执行 SQL 并将结果缓存。

  • 执行 INSERT操作后,缓存被清空。

  • 第二次查询 empId=1 时,由于缓存已失效,会再次执行 SQL。

2. 手动清空缓存

除了执行增删改操作外,还可以通过调用 SqlSession 的 clearCache() 方法手动清空一级缓存。这种方式适用于需要强制刷新缓存的场景。

示例代码:

CacheMapperTest.java 代码

@Test
public void testCacheInvalidationByClearCache() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
    Emp emp1 = mapper.getEmpById(1);
    System.out.println(emp1);
    sqlSession.clearCache();
    Emp emp2 = mapper.getEmpById(1);
    System.out.println(emp2);
}

输出结果

DEBUG ==> Preparing: SELECT * FROM t_emp WHERE emp_id = ?;
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
Emp{empId=1, empName='张三', age=20, gender='男'}

DEBUG ==> Preparing: SELECT * FROM t_emp WHERE emp_id = ?;
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
Emp{empId=1, empName='张三', age=20, gender='男'}

分析:

  • 第一次查询 empId=1 时,会执行 SQL 并将结果缓存。

  • 调用 clearCache() 方法后,缓存被清空。

  • 第二次查询 empId=1 时,由于缓存已失效,会再次执行 SQL。

3. 提交或回滚事务

当调用 SqlSession 的 commit() 或 rollback() 方法时,一级缓存会被清空。这是因为事务的提交或回滚可能改变了数据库的状态,缓存需要与数据库保持一致。

示例代码:

CacheMapperTest.java 代码

@Test
public void testCacheInvalidationByCommit() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
    Emp emp1 = mapper.getEmpById(1);
    System.out.println(emp1);
    sqlSession.commit();
    Emp emp2 = mapper.getEmpById(1);
    System.out.println(emp2);
}

输出结果

DEBUG ==> Preparing: SELECT * FROM t_emp WHERE emp_id = ?;
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
Emp{empId=1, empName='张三', age=20, gender='男'}

DEBUG ==> Preparing: SELECT * FROM t_emp WHERE emp_id = ?;
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
Emp{empId=1, empName='张三', age=20, gender='男'}

分析:

  • 第一次查询 empId=1 时,会执行 SQL 并将结果缓存。

  • 调用 commit() 方法后,缓存被清空。

  • 第二次查询 empId=1 时,由于缓存已失效,会再次执行 SQL。

4. 关闭 SqlSession

当 SqlSession 被关闭时,一级缓存也会被清空。这是因为一级缓存的生命周期与 SqlSession 绑定,SqlSession 关闭后,缓存自然失效。

示例代码:

CacheMapperTest.java 代码

@Test
public void testCacheInvalidationByClose() {
    SqlSession sqlSession = SqlSessionUtil.getSqlSession();
    CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
    Emp emp1 = mapper.getEmpById(1);
    System.out.println(emp1);
    sqlSession.close();
    SqlSession newSqlSession = SqlSessionUtil.getSqlSession();
    CacheMapper newMapper = newSqlSession.getMapper(CacheMapper.class);
    Emp emp2 = newMapper.getEmpById(1);
    System.out.println(emp2);
}

输出结果

DEBUG ==> Preparing: SELECT * FROM t_emp WHERE emp_id = ?;
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
Emp{empId=1, empName='张三', age=20, gender='男'}

DEBUG ==> Preparing: SELECT * FROM t_emp WHERE emp_id = ?;
DEBUG ==> Parameters: 1(Integer)
DEBUG <== Total: 1
Emp{empId=1, empName='张三', age=20, gender='男'}

分析:

  • 第一次查询 empId=1 时,会执行 SQL 并将结果缓存。

  • 关闭 SqlSession 后,缓存被清空。

  • 重新获取 SqlSession 后,第二次查询 empId=1 时,由于缓存已失效,会再次执行 SQL。

5.总结

MyBatis 的一级缓存在以下四种情况下会失效:

  1. 执行了增删改操作INSERTUPDATEDELETE 操作会清空缓存。

  2. 手动清空缓存:调用 SqlSession.clearCache() 方法。

  3. 提交或回滚事务:调用 SqlSession.commit() 或 rollback() 方法。

  4. 关闭 SqlSessionSqlSession 关闭后,缓存会被清空。