当前位置: 首页 > 文档资料 > MyBatis 入门教程 >

MyBatis 缓存

优质
小牛编辑
133浏览
2023-12-01

1. 前言

频繁地查询必然会给数据库带来巨大的压力,为此 MyBatis 提供了丰富的缓存功能。缓存可以有效的提升查询效率、缓解数据库压力,提高应用的稳健性。

MyBatis 的缓存有两层,默认情况下会开启一级缓存,并提供了开启二级缓存的配置。本小节我们将一起学习 MyBatis 的缓存,充分地了解和使用它。

2. 一级缓存

MyBatis 一级缓存是默认开启的,缓存的有效范围是一个会话内。一个会话内的 select 查询语句的结果会被缓存起来,当在该会话内调用 update、delete 和 insert 时,会话缓存会被刷新,以前的缓存会失效。

2.1 使用一级缓存

下面,我们以一个简单的例子来看看 MyBatis 的一级缓存是如何工作的。

package com.imooc.mybatis.cache;

import com.imooc.mybatis.mapper.UserMapper;
import com.imooc.mybatis.model.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

@SuppressWarnings({"Duplicates"})
public class CacheTest1 {
  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 session = sqlSessionFactory.openSession();
    // 得到 mapper
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 查询得到 user1
    User user1 = userMapper.selectUserById(1);
    System.out.println(user1);
    // 查询得到 user2
    User user2 = userMapper.selectUserById(1);
    // 通过 == 判断 user1 和 user2 是否指向同一内存区间
    System.out.println(user1 == user2);
    session.commit();
    session.close();
  }
}

结果:

User{id=1, username='peter-gao', age=180, score=1000}
true

在这个例子中,我们连续两次调用了 userMapper 的 selectUserById 方法,但是在程序输出中,user1 和 user2 却指向了同一块内存区域。这就是 MyBatis 缓存的作用,当第二次调用查询时,MyBatis 没有查询数据库而是直接从缓存中拿到了数据。

2.2 弃用一级缓存

2.2.1 select 配置关闭缓存

select 默认会启用一级缓存,我们也可通过配置来关闭掉 select 缓存。

如下,我们通过 flushCache 属性来关闭 select 查询的缓存。

<select flushCache="true" parameterType="java.lang.Integer"
        resultType="com.imooc.mybatis.model.User">
  SELECT * FROM imooc_user WHERE id = #{id}
</select>

再次运行程序,结果如下:

User{id=1, username='peter-gao', age=180, score=1000}
false

此时 user1 与 user2 不再指向同一内存区,缓存失效了。

2.2.2 调用 insert、update、delete 刷新缓存

一般情况下,我们都推荐开启 select 的缓存,因为这会节省查询时间。当然在一个会话中,调用 insert、update、delete 语句时,会话中的缓存也会被刷新。

如下:

UserMapper userMapper = session.getMapper(UserMapper.class);
User user1 = userMapper.selectUserById(1);
System.out.println(user1);
User user = new User();
user.setUsername("cache test");
user.setAge(10);
user.setScore(100);
userMapper.insertUser(user);
User user2 = userMapper.selectUserById(1);
System.out.println(user1 == user2);
session.commit();
session.close();
User{id=1, username='peter', age=18, score=100}
false

在第一个查询调用前,我们先进行了一次 insert 操作,此时会刷新缓存,user1 和 user2 又没有指向同一处内存。

3. 二级缓存

MyBatis 二级缓存默认关闭,我们可以通过简单的设置来开启二级缓存。二级缓存的有效范围为一个 SqlSessionFactory 生命周期,绝大多数情况下,应用都会只有一个 SqlSessionFactory,因此我们可以把二级缓存理解为全局缓存。

3.1 全局可用

在 MyBatis 全局配置文件中,即 mybatis-config.xml 文件,二级缓存可由 settings 下的 cacheEnabled 属性开启。如下:

<settings>
  <setting name="cacheEnabled" value="true"/>
</settings>

当打开 cacheEnabled 属性后,二级缓存全局可用。

TIPS:注意,这里是可用,cacheEnabled 的默认值其实也是 true,即全局可用,由于二级缓存需要对 mapper 配置后才真正生效,简单来说就是双层开关。当将其设置为 false 后,则全局关闭,mapper 中即使配置了,二级缓存也会失效。

3.2 mapper 中开启

3.2.1 xml 开启

在二级缓存全局可用的情况下,mapper 才可通过 cache 配置开启二级缓存。如,在 UserMapper.xml 文件中开启二级缓存:

<cache/>

这种情况下,缓存的行为如下:

  • mapper 下的所有 select 语句会被缓存;
  • mapper 下的 update,insert,delete 语句会刷新缓存;
  • 使用 LRU 算法来回收对象;
  • 最大缓存 1024 个对象;
  • 缓存可读、可写。
  • 缓存不会根据时间来刷新。

cache 提供了诸多属性来修改缓存行为,示例如下:

 <cache
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"/>

这个例子下的缓存使用 FIFO 算法来回收对象,并每隔 60 秒刷新一次,最多缓存 512 个对象,且缓存只可读。

cache 有 4 个属性可配置,从而改变缓存的行为。

属性描述
eviction回收策略,默认 LRU,可选择的有 FIFO(先进先出),SOFT(软引用),WEAK(弱引用)
flushInterval刷新时间
size最多缓存对象数
readOnly是否只读

3.2.2 注解开启

如果你不使用 mapper.xml 文件,也可以使用注解来开启。

如下:

package com.imooc.mybatis.mapper;

import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.cache.decorators.FifoCache;

@Mapper
@CacheNamespace(
  eviction = FifoCache.class,
  flushInterval = 60000,
  size = 512,
  readWrite = false
)
public interface BlogMapper {
}

注解 CacheNamespace 的配置与 xml 配置保持一致,唯一区别在于若使用注解,那么 eviction 属性需直接给出缓存实现类。

3.3 缓存共享

3.3.1 xml 共享

有时候,我们想在不同的 mapper 中共享缓存,为了解决这类问题,MyBatis 提供了 cache-ref 配置。

使用也很简单,如下:

<cache-ref namespace="com.imooc.mybatis.mapper.UserMapper"/>

mapper 由 namespace 来唯一标识,因此只需在另一个 mapper 文件中添加上 cache-ref 配置,并加上相应的 namespace 即可。

这样当前的 mapper 可以共享来自 UserMapper 的缓存。

3.3.2 注解共享

同样的,我们也可以使用注解来共享缓存。

如下:

@CacheNamespaceRef(UserMapper.class)
public interface BlogMapper {
}

这里,BlogMapper 共享了 UserMapper 的缓存。

TIPS: 注意,CacheNamespaceRef 与 CacheNamespace 不能共存,既然选择了共享就不能再独立开辟缓存区了。

4. 小结

  • MyBatis 的一级缓存默认可用,有效范围小,不会影响到其它会话,因此无特殊情况,不推荐丢弃一级缓存。
  • MyBatis 二级缓存默认使用程序内存缓存,但这显然不够安全,一般情况下我们都推荐使用 Redis 等专业的缓存。

最后更新:

类似资料

  • 当你使用本地(在内存中)缓存时,服务器可以缓存一些信息并快速地检索它,但是其他服务器不能访问这个缓存数据,他们需要到数据库中查询同样的信息。 如果你喜欢使用分布式缓存让其他服务器访问缓存的数据,由于它有一些序列化/反序列化和网络延迟开销,则需要注意:在某些情况下,它可能会降低性能。 缓存需要处理的另一个问题:缓存失效。 There are only two hard things in Compu

  • Serenity 提供一些缓存抽象和实用功能让你更容易地使用本地缓存。 术语 本地(local) 的意思是指在本地内存中缓存项目(因此没有涉及到序列化)。 当你的应用程序在网站群(web farm) 中部署时,本地缓存可能还不够或者有时合适。我们将在 分布式缓存 章节中讨论该场景。

  • Web 应用程序可能需要为成百上千甚至更多的用户同时提供服务。如果你没有采取必要的措施,在这种负载下,你的网站可能会崩溃或变得没有响应。 假设在主页显示最后 10 条新闻,并且平均每分钟有上千名用户访问此页面。你可能为每个用户通过查询数据库来显示页面视图信息: SELECT TOP 10 Title, NewsDate, Subject, Body FROM News ORDER BY NewsD

  • 一个动态网站的基本权衡点就是,它是动态的。 每次用户请求一个页面,Web服务器将进行所有涵盖数据库查询到模版渲染到业务逻辑的请求,用来创建浏览者需要的页面。从开销处理的角度来看,这比你读取一个现成的标准文件的代价要昂贵的多。 对于大多数网络应用程序,这个开销不是很大的问题。我们的应用不是washingtonpost.com or slashdot.org; 他们只是中小型网站,而且只有那么些流量而

  • 缓存的原则 缓存是一个大型系统中非常重要的一个组成部分。在硬件层面,大部分的计算机硬件都会用缓存来提高速度,比如 CPU 会有多级缓存、RAID 卡也有读写缓存。在软件层面,我们用的数据库就是一个缓存设计非常好的例子,在 SQL 语句的优化、索引设计、磁盘读写的各个地方,都有缓存,建议大家在设计自己的缓存之前,先去了解下 MySQL 里面的各种缓存机制,感兴趣的可以去看下High Performa

  • 缓存是现代高并发应用程序的重要组成部分。即使你的 web 应用程序目前还没有那么高的并发量,但在之后的发展中极有可能会遇到高并发的应用场景,因此从一开始就使用缓存设计程序是一个好主意。 本地缓存 分布式缓存 二级缓存