程序员社区

5. MyBatis 缓存

MyBatis 缓存

1. 什么是MyBatis缓存

上文介绍的 MyBatis 延迟加载,解决的是多表关联查询情况下的效率问题,但是对于单表查询,延迟加载没有作用,MyBatis 提供了缓存机制来解决单表查询情况下的效率问题。

使用缓存也是通过减少 Java 程序和数据库的交互次数来提高查询效率。比如,第一次查询出某个对象之后,MyBatis 会自动将它存入缓存,当下一次查询该对象时,可以直接从缓存中获取,而不必再次访问数据库。


5. MyBatis 缓存插图

如果执行删除、修改操作,MyBatis 会自动清空缓存,从而保证数据的时效性与正确性。

MyBatis 有两种缓存方式:一级缓存与二级缓存,二者的区别在于作用域不同。


2. 一级缓存

  1. MyBatis 自带一级缓存,SqlSession 级别,默认开启,并且无法关闭。

  2. MyBatis 的一级缓存是 SqlSession 级别的缓存,在操作数据库时需要构建 SqlSession 对象,在对象里有一个 HashMap 用于存储缓存数据,不同的 SqlSession 之间缓存数据(HashMap)是互不影响的。

  3. 一级缓存的作用域是 SqlSession 范围的,当在同一个 SqlSession 中执行两次相同的 sql 语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存)中,第二次查询时会从缓存中读取数据,而不再去底层进行数据库查询,从而提高了查询效率。

  4. 需要注意的是,如果 SqlSession 执行 DML操作(删除和更新),并执行 commit() 操作,则 MyBatis 会自动清空 SqlSession 中的一级缓存,这样做的目的是为了保证缓存数据的时效性和正确性,避免出现读脏现象。

  5. 当一个 SqlSession 结束后(SqlSession.close()),该 SqlSession 中的一级缓存也就不存在了,Mybatis 默认开启一级缓存,不需要进行任何配置。

以单表查询为例,介绍 MyBatis 缓存

在这里插入图片描述

1、创建实体类 User

package com.training.entity;

public class User {
    private Integer id;
    private String name;
    private Double score;

    public User(){
        
    }

    public User(Integer id, String name, Double score) {
        this.id = id;
        this.name = name;
        this.score = score;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getScore() {
        return score;
    }

    public void setScore(Double score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

2、自定义 UserMapper 接口

package com.training.mapper;

import com.training.entity.User;

public interface UserMapper {
    public String findById(Integer id);
}

3、SQL 配置文件 UserMapper.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.training.mapper.UserMapper">
    <select id="findById" parameterType="int" resultType="com.training.entity.User">
        select * from user where id=#{id};
    </select>
</mapper>

4、全局配置文件 config.xml

<?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>
        <!-- 打印SQL -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--数据源-->
    <environments default="dev">
        <!--可以配置多个环境-->
        <environment id="dev">
            <!--配置JDBC事务管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!-- POOLED配置JDBC数据库连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8"></property>
                <property name="username" value="root"></property>
                <property name="password" value="123456"></property>
            </dataSource>
        </environment>
    </environments>

     <!--注册UserMapper.xml -->
    <mappers>
        <mapper resource="com/training/mapper/UserMapper.xml"></mapper>
    </mappers>

</configuration>

5、编写 Test 测试类

public class Test {
    public static void main(String[] args) {
        //加载MyBatis配置信息
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        //构建SQLSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new
                SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        //获取SqlSession
        SqlSession sqlSession = factory.openSession();
        //获取实现接口的代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user1 = mapper.findById(1);
        System.out.println(user1);
        User user2 = mapper.findById(1);
        System.out.println(user2);
    }
}

控制台打印的信息如下:

在这里插入图片描述

从结果中可以看出,Java 程序执行了两次查询操作,但是 MyBatis 底层仅执行了一次 SQL 查询。查询出两个对象,第一个对象是通过SQL查询的,并 保存到缓存中,第二个对象是直接从缓存中获取的。

public class Test {
    public static void main(String[] args) {
        //加载MyBatis配置信息
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        //构建SQLSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new
                SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        //获取SqlSession
        SqlSession sqlSession1 = factory.openSession();
        //获取实现接口的代理对象
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = mapper1.findById(1);
        System.out.println(user1);
        
        SqlSession sqlSession2 = factory.openSession();
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = mapper2.findById(1);
        System.out.println(user2);
    }
}

控制台打印的信息如下:

在这里插入图片描述

说明:一级缓存是 SqlSession 级别的缓存,在操作数据库时需要构建 SqlSession 对象,在对象里有一个 HashMap 用于存储缓存数据,不同的 SqlSession 之间缓存数据(HashMap)是互不影响的。


3. 二级缓存

一级缓存不需要进行任何配置,可以直接使用。当一级缓存失效时,可以开启二级缓存,同时实现数据共享的功能,二级缓存是比一级缓存作用域更大的缓存机制,是 Mapper 级别的,只要是同一个 Mapper,无论使用多少个 SqlSession 来创建,数据都是共享的,多个 SqlSession 可以共有二级缓存。

MyBatis 二级缓存默认是关闭的,需要使用的时候,可以通过手动设置来开启。

1、config.xml 配置文件中开启二级缓存

<settings>
    <!-- 打印SQL -->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!-- 开启二级缓存 -->
    <setting name="catchEnabled" value="true"></setting>
</settings>

2、UserMapper.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.training.mapper.UserMapper">
    <!-- 开启二级缓存 -->
    <cache></cache>
    
    <select id="findById" parameterType="int" resultType="com.training.entity.User">
        select * from t_user where id=#{id};
    </select>
</mapper>

3、实体类 User 实现 Serializable 接口

package com.training.entity;

import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
    private Integer id;
    private String name;
    private Double score;
}

4、测试二级缓存

public class Test {
    public static void main(String[] args) {
        //加载MyBatis配置信息
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("config.xml");
        //构建SQLSessionFactoryBuilder
        SqlSessionFactoryBuilder builder = new
                SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        //获取SqlSession
        SqlSession sqlSession1 = factory.openSession();
        //获取实现接口的代理对象
        UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
        User user1 = mapper1.findById(1);
        System.out.println(user1);
        //关闭sqlSession1或sqlSession1.commit(),将一级缓存的数据同步到二级缓存
        sqlSession1.close();
        SqlSession sqlSession2 = factory.openSession();
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = mapper2.findById(1);
        System.out.println(user2);
    }
}

控制台输出的信息如下:

在这里插入图片描述

SqlSession 在没有提交的情况下,不会将数据同步到二级缓存中,所以会导致第二次查询仍然要执行 SQL,所以一般执行完毕之后需要关闭 SqlSession,关闭的同时会将一级缓存中的数据自动提交到二级缓存。

但是在实际项目开发中,使用的是 ehcache 二级缓存,使用方式与上面相似

Step1:pom.xml 添加相关依赖

<!--mybatis 启用ehcache 二级缓存-->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.8.3</version>
</dependency>

Step2:添加 ehcache.xml(与 config.xml 全局配置文件同级)

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <diskStore/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

Step3:config.xml 配置开启二级缓存

 <settings>
     <!-- 打印SQL -->
     <setting name="logImpl" value="STDOUT_LOGGING"/>
     <!-- 开启延迟加载 -->
     <setting name="lazyLoadingEnabled" value="true"></setting>
     <!-- 开启二级缓存 -->
     <setting name="cacheEnabled" value="true"></setting>
</settings>

Step4:UserMapper.xml 中配置二级缓存

<!-- 开启二级缓存 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache">
    <!-- 缓存创建之后,最后⼀次访问缓存的时间⾄缓存失效的时间间隔 -->
    <property name="timeToIdleSeconds" value="3600"/>
    <!-- 缓存⾃创建时间起⾄失效的时间间隔 -->
    <property name="timeToLiveSeconds" value="3600"/>
    <!-- 缓存回收策略,LRU表示移除近期使⽤最少的对象 -->
    <property name="memoryStoreEvictionPolicy" value="LRU"/>
</cache>

Step5:与上面介绍有所不同的是,实体类不需要实现序列化接口

package com.training.entity;

import lombok.Data;

@Data
public class User {
    private Integer id;
    private String name;
    private Double score;
}

同样的测试代码,控制台的输出结果如下:

在这里插入图片描述


4. 总结

  1. MyBatis 框架的缓存分为两种:一级缓存和二级缓存,一级缓存是 SqlSession 级别的,二级缓存是 Mapper 级别的,使用的时候需要注意两种缓存的区别,一级缓存是默认开启且无法关闭的,二级缓存需要手动开启。

  2. 缓存机制和延迟加载功能类似,都是通过减少 Java 程序和数据库的交互次数来提高系统的运行速度,缓存机制更多针对于单表查询,延迟加载更多针对多表关联查询。

  3. 缓存机制使用的前提是两次查询之间没有数据修改与删除的操作,如果有修改和删除的操作,那么缓存就会自动清空,从而保证数据的时效性与正确性。

赞(0) 打赏
未经允许不得转载:IDEA激活码 » 5. MyBatis 缓存

一个分享Java & Python知识的社区