MyBatis缓存

在当今高并发的互联网应用中,数据库的查询性能往往是系统的瓶颈。频繁地对数据库进行相同的查询,会导致大量的 I/O 操作和数据库负载,从而降低系统的响应速度。为了解决这个问题,缓存技术应运而生。MyBatis 作为一款优秀的持久层框架,内置了强大的缓存机制,可以显著提升数据库查询性能。本文将详细解析 MyBatis 的一级缓存和二级缓存。

一、什么是缓存?

1. 缓存的定义

缓存(Cache)是指将计算过程中产生的中间数据或结果数据,临时存储在速度更快的存储介质(通常是内存)中,以便后续需要时可以快速访问,从而避免重复计算或重复从慢速存储介质(如磁盘、数据库)中读取数据。

简单来说,缓存就是存储在内存中的临时数据

2. 为什么使用缓存?

使用缓存主要有以下几个核心目的:

减少数据库交互:避免了对数据库的频繁查询,极大地减轻了数据库的压力。

提升系统性能:内存的读写速度远快于磁盘,从缓存中读取数据可以显著缩短响应时间,提高系统吞吐量。

降低系统开销:减少了网络 I/O 和数据库 I/O,降低了系统的整体资源消耗。

3. 什么样的数据适合放入缓存?

并非所有数据都适合缓存。通常,适合放入缓存的数据需要满足以下特点:

查询频率高:被大量用户或频繁访问的数据。

修改频率低:数据相对稳定,不会经常发生变化。如果数据频繁修改,会导致缓存与数据库的数据不一致,需要频繁更新缓存,反而可能得不偿失。

一致性要求不高:对于一些实时性要求不高的数据,即使缓存中的数据有短暂的延迟,也是可以接受的。

典型的例子包括:网站的分类信息、用户的基本配置、商品的基础信息等。

二、MyBatis 缓存机制

MyBatis 提供了非常强大且易于配置的查询缓存特性。它默认定义了两级缓存:一级缓存二级缓存

  • 一级缓存 :默认开启,作用于 SqlSession 级别。
  • 二级缓存 :默认关闭,需要手动开启和配置,作用于 Mappernamespace 级别。

2.1. 一级缓存

一级缓存也称为本地缓存,它是 MyBatis 中默认开启的缓存。

2.1.1工作原理

一级缓存的生命周期与 SqlSession 完全相同。在同一次数据库会话(即同一个 SqlSession 实例)期间,如果执行了完全相同的查询语句,MyBatis 会将查询结果存入一级缓存中。当再次执行相同的查询时,MyBatis 会优先从一级缓存中获取数据,而不会再去查询数据库,从而提高查询效率。

2.1.2代码演示

java 复制代码
public class UserTest {
    private InputStream in = null;
    private SqlSession session = null;
    private UserDao mapper = null;

    /**
     * 测试查询所有的方法
     */
    @Test
    public void findById() throws IOException {
        //加载主配置文件,目的是为了构建SqlSessionFactory对象
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //通过SqlSessionFactory工厂对象创建SqlSesssion对象
        session = factory.openSession();
        //通过Session创建UserDao接口代理对象
        mapper = session.getMapper(UserDao.class);

        User user1 = mapper.findById(1);
        System.out.println(user1.toString());
        System.out.println("-----------------");
        User user2 = mapper.findById(1);
        System.out.println(user2.toString());
        System.out.println(user1 == user2);
        //释放资源
        session.close();
        in.close();
    }
}

2.1.3缓存失效的情况

sqlSession不同

sqlSession相同,查询条件不同

sqlSession相同,两次查询之间执行了增删改操作!

sqlSession相同,手动清除一级缓存

1.sqlSession不同

2.sqlSession相同,查询条件不同,多次查询不同的情况,不会导致缓存失效

3.sqlSession相同,两次查询之间执行了增删改操作!

4.sqlSession相同,手动清除一级缓存

2.2二级缓存

2.2.1工作原理

当一个 SqlSession 关闭时,其对应的一级缓存中的数据会被写入到二级缓存中。此后,其他由同一个 SqlSessionFactory 创建的 SqlSession 在执行相同的查询时,就可以直接从二级缓存中获取数据,而无需再访问数据库。

2.2.2二级缓存开启的条件

1.在核心配置文件中,设置全局属性caheEnable="true"。

2.在映射件中置

3.查询数据所转换的实体类类型必须实现序列化接口

4.二级缓存必须在SqlSession关闭或提交之后有效

2.2.3二级缓存失效的情况

两次查询之间行了任意的增删改,会使得一级二级缓存同时失效

2.2.4开启二级缓存

在SqlMapConfig.xml配置文件中开启二级缓存

java 复制代码
<!‐‐ 开启二级缓存 ‐‐>
<settings>
    <!--开启二级缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

在UserDao.xml配置文件声明使用二级缓存

java 复制代码
<!--使用二级缓存-->
<cache/>

查询数据所转换的实体类类型必须实现序列化接口

序列化是指将一个对象的状态转换成字节流的过程,而反序列化则是将字节流恢复成对象状态的过程。

java 复制代码
public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    // get set方法 .....
 }

二级缓存必须在SqlSession关闭或提交之后有效

2.2.5代码演示

java 复制代码
@Test
public void findById() throws IOException {
    // 1.加载SqlMapConfig配置文件
    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    //2.创建sqlSessionFactory工厂
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    //3.sqlSessionFactory创建sqlSession
    SqlSession sqlSession = factory.openSession();
    SqlSession sqlSession2 = factory.openSession();

    //4.通过Session创建UserDao接口代理对象
    UserDao mapper = sqlSession.getMapper(UserDao.class);
    User user1 = mapper.findById(1);
    System.out.println(user1.toString());
    // 将其一级缓存的数据放进二级缓存中,并清空一级缓存
    sqlSession.close();


    System.out.println("-----------------");
    UserDao mapper2 = sqlSession2.getMapper(UserDao.class);
    User user2 = mapper2.findById(1);
    System.out.println(user2.toString());
    // 将其一级缓存的数据放进二级缓存中,并清空一级缓存
    sqlSession2.close();

    System.out.println(user1 == user2);
    
    resourceAsStream.close();
}

测试结果

2.2.6重要结论

打印发现2个对象的地址值不一样,但是确实只发送了一次SQL语句的查询,二级缓存中存储的是数据,不是对象。

2.2.7Catch参数的具体细节

1.eviction(收回策略)

LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。

FIFO(先进先出):按对象进入缓存的顺序来移除它们。

SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。

WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。

2.flushinterval(刷新间隔) 可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。

默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。

3.size(引用数目)

可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024 。

4.readOnly(只读) 属性可以被设置为 true / false。

true:只读缓存:会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改, 这提供了很重要的性能优势。

false读写缓存: 通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是 false。

三、Mybatis缓存查询顺序

1.先查询二级缓存,因为二级缓存中可能会有其他程序查询出来的数据,可以直接拿来使用

2.如果二级缓存未命中,再查询一级缓存

3.如果一级缓存也没有命中,则查询数据库

4.SqlSession关闭之后,一级缓存的数据会写入二级缓存

相关推荐
7澄12 小时前
MyBatis缓存详解:一级缓存、二级缓存与实战优化
缓存·mybatis·一级缓存
PacosonSWJTU3 小时前
Guava缓存使用入门
java·缓存·guava
胡闹544 小时前
MyBatis-Plus 更新字段为 null 为何失效?
java·数据库·mybatis
侠客行03174 小时前
Mybatis入门到精通 二
java·mybatis·源码阅读
侠客行031715 小时前
Mybatis入门到精通 一
java·mybatis·源码阅读
java1234_小锋17 小时前
Redis的热Key问题如何解决?
数据库·redis·缓存
鸽鸽程序猿18 小时前
【Redis】事务
数据库·redis·缓存
赵得C19 小时前
完整 Oracle 12c 分页 Demo(Spring Boot+MyBatis+PageHelper)
spring boot·oracle·mybatis
今晚务必早点睡19 小时前
Redis——快速入门第七课:Redis 为什么这么快?
数据库·redis·缓存