Mybatis的缓存

1、MyBatis 缓存的基本介绍

缓存是一般的 ORM 框架都会提供的功能,目的就是提升查询的效率和减少数据库的压力。缓存是存在内存中的临时数据,将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。经常查询并且不经常改变的数据就可以使用缓存。

MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

  • 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  • 为了提高可扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。

2、一级缓存(本地缓存)

一级缓存也叫本地缓存,一级缓存是默认开启的。

同一个 SqlSession 对象, 在参数和 SQL 完全一样的情况下,只执行一次 SQL 语句(如果缓存没有过期)。只有在参数和 SQL 完全一样的情况下, 才会有这种情况。

示例:

下面示例需要开启 mybatis 日志才能看到效果。

假设我们在一个Session中查询两次相同记录,此时除了第一次查询外,后面的查询都会直接使用缓存,而不会再去查询数据库。

@Test
public void test01() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = factory.openSession();

    UserDao userDao = session.getMapper(UserDao.class);
    User user = userDao.findUserById(1);
    
    System.out.println("=======分隔符=======");

    User user2 = userDao.findUserById(1);
    System.out.println(user == user2);
}

日志输出如下:

可以看到,第一次查询发送了 SQL 语句, 后返回了结果;第二次查询没有发送 SQL 语句, 直接从内存中获取了结果。两次查询获取到的对象是相同的,这说明第二次查询是直接从缓存拿到的结果。

2.1、一级缓存的特性

一级缓存特性:

  • 在同一个 SqlSession 中,相同SQL并且参数相同才会命中缓存。在同一个 SqlSession 中,Mybatis 会把执行的 SQL 和参数通过算法生成缓存的键 key, 将键 key 和 SQL 查询的结果作为 value 值存放在一个 Map 中, 如果后续的键一样,即 sql 和参数意义, 则直接从 Map 中获取缓存数据
  • 不同的 SqlSession 之间的缓存是相互隔离的。例如查询一条 SQL 后又重新创建了一个 sqlsession 对象,则这两个对象之间的缓存是不共用的
  • 查询不同的 mapper 的 xml 配置文件的 sql 语句,缓存不共用
  • 任何的增删改( UPDATE、 INSERT、DELETE)语句都会清空缓存。
  • 可以在 mapper 文件的 select 标签中配置 flushCache=“true” ,如 <select id="selectByPrimaryKey" flushCache="true" ...>,这样每次查询都会将这个 sqlsession 中的所有缓存清空。或者手动调用清空缓存的方法 sqlSession.clearCache(); 也可清除缓存。

3、二级缓存(全局缓存)

二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存。二级缓存存在于 SqlSessionFactory 生命周期中。二级缓存需要手动开启和配置,它是基于namespace级别的缓存。

缓存机制:会先命中二级缓存,二级缓存中没有再看一级缓存,都没有才去查数据库。

3.1、使用二级缓存 

在 mybatis 的配置文件 mybatis-config.xml 中添加 <setting name="cacheEnabled" value="true"/> 配置:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <settings>
        <!--配置日志-->
        <setting name="logImpl" value="STDOUT_LOGGING" />
        <!--全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>

    ...
</configuration>

在 mapper 的配置文件 *Mapper.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="dao.UserDao">
    <!--
        在当前Mapper.xml中使用二级缓存。
        也可以不配置属性,直接使用 <cache />。下面配置中的属性说明如下:
            1、缓存的清除策略为 LRU(默认值),即最近最少使用:移除最长时间不被使用的对象。
            2、每隔 60 秒刷新
            3、最多可以存储结果对象或列表的 512 个引用
            4、返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突
    -->
    <cache
        eviction="FIFO"
        flushInterval="60000"
        size="512"
        readOnly="true"/>

    <select id="findUserById" resultType="entity.User">
        select * from user where id = #{id}
    </select>
</mapper>

注意,如果是直接使用 <cache />,即不配置属性,此时实体类即 User 类需要实现序列化 Serializable 接口。

测试代码:

@Test
public void test01() throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

    SqlSession session = factory.openSession();
    UserDao userDao = session.getMapper(UserDao.class);
    User user = userDao.findUserById(1);
    //需要手动提交事务
    session.commit();
    session.close();

    System.out.println("=======分隔符=======");

    SqlSession session2 = factory.openSession();
    UserDao userDao2 = session2.getMapper(UserDao.class);
    User user2 = userDao2.findUserById(1);

    System.out.println(user == user2);
}

日志输出如下:

可以看到,在同一个 mapper 配置文件中的 SQL 语句,即使是不同的 sqlsession 对象,仍然可以命中缓存。

只有查询才有缓存,我们可以根据数据是否需要缓存来配置该条查询语句是否缓存:

 <select id="getUserById" resultType="user" useCache="true">
        select * from user where id = #{id}
</select>

3.2、二级缓存的特性

二级缓存的特性:

  • 开启了二级缓存后,同一个Mapper中的相同 SQL 相同参数的查询可以在二级缓存中拿到数据。

  • 查出的数据都会被默认先放在一级缓存中,只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中。

原文地址:https://www.cnblogs.com/wenxuehai/p/15357086.html