首页 > 其他分享 >11. MyBatis的缓存

11. MyBatis的缓存

时间:2022-10-21 11:14:42浏览次数:49  
标签:11 salary 缓存 age empId sex MyBatis public

一、MyBatis的缓存机制

  MyBatis 在执行 DQL(select 语句)的时候,将查询结果放到缓存(内存)当中。如果下一次还是执行完全相同的 DQL 语句,直接从缓存中拿取数据,不再查询数据库,不在从硬盘上查找数据。这样,可以提高程序的执行效率。缓存机制的主要使用减少 IO(读文件和写文件) 的方式来提高效率。

  MyBatis 的缓存包括:

  • 一级缓存:将查询到的数据存储到 SqlSession 中
  • 二级缓存:将查询到的数据存储到 SqlSessionFactory 中
  • 或者集成其它第三方缓存:比如 EhCache【Java语言开发的】、Memchae【C语言开发的】

缓存只针对 DQL 语句,也就是说缓存机制只对应 select 语句

二、MyBatis的一级缓存

  一级缓存是 SqlSession 级别的,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。一级缓存默认是开启的,不需要做任何配置。

  一级缓存失效的四种情况

  • 不同的 SqlSession 对应不同的一级缓存
  • 同一个 SqlSession 但是查询条件不同
  • 同一个 SqlSession 两次查询期间执行了任何一次增删改操作,并且和表没有关系
  • 同一个 SqlSession 两次查询期间手动清空了缓存,即执行了 clearCache()

数据的准备

CREATE DATABASE IF NOT EXISTS db_test;
USE db_test;

CREATE TABLE IF NOT EXISTS t_emp(
	emp_id INT PRIMARY KEY auto_increment,
	emp_name VARCHAR(20),
	age INT,
	sex VARCHAR(10),
	salary DOUBLE
);

INSERT INTO t_emp(emp_id,emp_name,age,sex,salary)
VALUES (null,'Sakura',10,"女",7300),
    (null,'Mikoto',14,"女",7000),
    (null,'Shana',15,'女',7100),
    (null,'Kikyō',18,'女',6700),
    (null,'Kagome',15,'女',6500);

导入jar包

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.29</version>
</dependency>

<!-- junit 测试程序 -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

创建核心配置文件

  创建 MyBatis 的核心配置文件,习惯上命名为 mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。配置 MyBatis 的核心配置文件中的标签必须按照固定的顺序:

properties --> settings --> typeAliases --> typeHandlers --> objectFactory --> objectWrapperFactory --> reflectorFactory --> plugins --> environments --> databaseIdProvider --> mappers

<?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>

    <!-- 引入数据库连接信息的配置文件 -->
    <properties resource="jdbc.properties"/>

    <!-- settings标签的内容是MyBatis极为重要的调整设置,它们会改变MyBatis的运行时行为 -->
    <settings>
        <!-- 指定MyBatis所有日志的具体实现,未指定时将自动查找 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
	<!-- 开启驼峰命名自动映射 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <!-- 将这个包下的所有的类全部起别名,别名就是类简名,不区分大小写 -->
        <package name="star.light.pojo"/>
    </typeAliases>

    <!-- environments:配置多个连接数据库环境 -->
    <environments default="development">
        <!-- environment:配置某个具体的环境 -->
        <environment id="development">
            <!-- 设置事务管理方式 -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源 -->
            <dataSource type="POOLED">
                <!-- 设置连接数据库的驱动 -->
                <property name="driver" value="${jdbc.driver}"/>
                <!-- 设置连接数据库的地址 -->
                <property name="url" value="${jdbc.url}"/>
                <!-- 设置连接数据库的用户名 -->
                <property name="username" value="${jdbc.username}"/>
                <!-- 设置连接数据库的密码 -->
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入映射文件-->
    <mappers>
        <!-- 从Mapper接口所在的包的路径中加载 -->
        <package name="star.light.mapper"/>
    </mappers>
</configuration>

  数据库连接信息的配置文件:jdbc.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db_test
jdbc.username=root
jdbc.password=abc123

创建MyBatis映射文件

  MyBatis 的映射文件存放的目录要与 Mapper 接口的路径一致。

<?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">
<!--
    namespace用来指定命名空间的,防止id重复
    如果在多个Mapper文件中,SQL语句的id重复,这时可以在Java程序中写namespace.id
    实质上,本质上,MyBatis中的SQLId的完整写法为namespace.id
-->
<mapper namespace="star.light.mapper.EmpMapper">
  
    <!-- Employee selectEmpById(Integer empId); -->
    <select id="selectEmpByEmpId" resultType="Employee">
        select emp_id, emp_name, age, sex, salary from t_emp where emp_id = #{empId}
    </select>

    <!-- int insertEmployee(Employee employee); -->
    <insert id="insertEmployee">
        insert into t_emp(emp_id,emp_name,age,sex,salary) values (#{empId},#{empName},#{age},#{sex},#{salary})
    </insert>
</mapper>

创建Mapper接口

package star.light.mapper;

import star.light.pojo.Employee;

public interface EmpMapper {

    /**
     * MyBatis 面向接口编程的两个一致:
     *  1、映射文件的 namespace 要和 mapper 接口的全类名保持一致
     *  2、映射文件中 SQL 语句的 id 要和 mapper 接口中方法名一致
     */

    Employee selectEmpByEmpId(Integer empId);
    int insertEmployee(Employee employee);
}

创建实体类

package star.light.pojo;

public class Employee {
    private Integer empId;
    private String empName;
    private Integer age;
    private String sex;
    private Double salary;

    public Employee() {
    }

    public Employee(Integer empId, String empName, Integer age, String sex, Double salary) {
        this.empId = empId;
        this.empName = empName;
        this.age = age;
        this.sex = sex;
        this.salary = salary;
    }

    public Integer getEmpId() {
        return empId;
    }

    public void setEmpId(Integer empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "empId=" + empId +
                ", empName='" + empName + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", salary=" + salary +
                '}';
    }
}

封装SqlSessionUtil工具类

package star.light.util;

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;

public class SqlSessionUtil {
    private static SqlSessionFactory sqlSessionFactory;

    // 为了防止new对象,工具类的构造方法一般都是私有的
    // 工具类中所有的方法都是静态的,直接采用类型即可调用,不需要new对象
    private SqlSessionUtil(){}

    // 静态代码块在类加载的时候执行
    // SqlSesionUtil工具类在进行第一次加载的时候,解析MyBatis和核心配置文件,创建SqlSessionFactory对象
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取会话对象
     * @return 会话对象
     */
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

测试程序

package star.light.test;

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import star.light.mapper.EmpMapper;
import star.light.pojo.Employee;
import star.light.util.SqlSessionUtil;


public class CacheTest {

    @Test
    public void testFirstLevelCache(){
        SqlSession sqlSession1 = SqlSessionUtil.getSqlSession();

        EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
        Employee employee1 = mapper1.selectEmpByEmpId(1);
        System.out.println(employee1);

        EmpMapper mapper2 = sqlSession1.getMapper(EmpMapper.class);
        Employee employee2 = mapper2.selectEmpByEmpId(1);
        System.out.println(employee2);

        // 获取不同的SqlSession对象,一级缓存失效
        SqlSession sqlSession2 = SqlSessionUtil.getSqlSession();
        EmpMapper mapper3 = sqlSession2.getMapper(EmpMapper.class);
        Employee employee3 = mapper3.selectEmpByEmpId(1);
        System.out.println(employee3);

        // 手动清空一级缓存,会使一级缓存失效
        sqlSession1.clearCache();

        // 在这里执行了INSERT、DELETE、UPDATE中的任意一个语句,并且与要操作的表没有关系都会使一级缓存失效
        int affectedRowCount = mapper3.insertEmployee(new Employee(null, "Akame", null, "女", 15000.0));
        System.out.println("affectedRowCount = " + affectedRowCount);

        Employee employee4 = mapper3.selectEmpByEmpId(1);
        System.out.println(employee4);

        // 程序执行到这里的时候,会将sqlsession1中的一级缓存写入到二级缓存中
        sqlSession1.close();
        // 程序执行到这里的时候,会将sqlsession2中的一级缓存写入到二级缓存中
        sqlSession2.close();
    }
}

三、MyBatis的二级缓存

  二级缓存是 SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。

  二级缓存开启的条件

  1. 在核心配置文件中,设置全局配置属性 cacheEnabled="true",默认为 true,不需要设置
  2. 在映射文件中设置标签 <cache> 标签
  3. 查询的数据所转换的实体类类型必须实现序列化的接口,也就是必须实现 java.io.Serialzable 接口
  4. 二级缓存必须在 SqlSession 关闭或提交之后有效,一级缓存中的数据才会被写入到二级缓存当中,此时二级缓存才可用

  二级缓存失效的情况

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

在映射文件中设置标签 <cache> 标签

<?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">
<!--
    namespace用来指定命名空间的,防止id重复
    如果在多个Mapper文件中,SQL语句的id重复,这时可以在Java程序中写namespace.id
    实质上,本质上,MyBatis中的SQLId的完整写法为namespace.id
-->
<mapper namespace="star.light.mapper.EmpMapper">
  
    <cache />

    <!-- Employee selectEmpById(Integer empId); -->
    <select id="selectEmpByEmpId" resultType="Employee">
        select emp_id, emp_name, age, sex, salary from t_emp where emp_id = #{empId}
    </select>

    <!-- int insertEmployee(Employee employee); -->
    <insert id="insertEmployee">
        insert into t_emp(emp_id,emp_name,age,sex,salary) values (#{empId},#{empName},#{age},#{sex},#{salary})
    </insert>
</mapper>

实体类实现序列化接口

package star.light.pojo;

import java.io.Serializable;

public class Employee implements Serializable {
    private Integer empId;
    private String empName;
    private Integer age;
    private String sex;
    private Double salary;

    public Employee() {
    }

    public Employee(Integer empId, String empName, Integer age, String sex, Double salary) {
        this.empId = empId;
        this.empName = empName;
        this.age = age;
        this.sex = sex;
        this.salary = salary;
    }

    public Integer getEmpId() {
        return empId;
    }

    public void setEmpId(Integer empId) {
        this.empId = empId;
    }

    public String getEmpName() {
        return empName;
    }

    public void setEmpName(String empName) {
        this.empName = empName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Double getSalary() {
        return salary;
    }

    public void setSalary(Double salary) {
        this.salary = salary;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "empId=" + empId +
                ", empName='" + empName + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", salary=" + salary +
                '}';
    }
}

在测试程序中添加如下测试方法

@Test
public void testSecondLevelCache() throws IOException {
    // 这里只有同一个SqlSessioFactory对象,二级缓存对应的SqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();

    EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
    EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);

    // 这行代码结束之后,实际上数据缓存到一级缓存中(sqlSession1是一级缓存)
    Employee employee1 = mapper1.selectEmpByEmpId(1);
    System.out.println(employee1);

    // 如果这里不关闭sqlSession1对象的话,二级缓存中还是没有数据
    sqlSession1.close();

    // 这行代码结束之后,实际上数据缓存到一级缓存中(sqlSession2是一级缓存)
    Employee employee2 = mapper2.selectEmpByEmpId(1);
    System.out.println(employee2);

    // 只要两次查询之间出现了增删改操作,二级缓存就会失效(一级缓存也会失效)
    int affectedRowCount = mapper2.insertEmployee(new Employee(null, "Akame", null, "女", 15000.0));
    System.out.println("affectedRowCount = " + affectedRowCount);

    Employee employee3 = mapper2.selectEmpByEmpId(1);
    System.out.println(employee3);

    // 程序执行到这里,会将sqlSesion2中一级缓存中的数据缓存到二级缓存中
    sqlSession2.close();
}

  二级缓存的相关配置

  • eviction 属性:缓存回收策略,默认的是 LRU。

    • LRU(Least Recently Used):最近最少使用的,优先淘汰在间隔时间内使用频率最低的对象。(其实还有一种淘汰算法LFU,最不常用)
    • FIFO(First in First out):先进先出,先进入二级缓存的对象最先被淘汰
    • SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象,具体算法和 JVM 的垃圾回收算法有关
    • WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象,具体算法和 JVM 的垃圾回收算法有关
  • flushInterval 属性:二级缓存的刷新间隔,单位毫秒

    • 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
  • size 属性:设置二级缓存中对多可存储的 Java 对象数,正整数,默认值 1024

    • 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
  • readOnly 属性:只读, true/false

    • true:只读缓存,多条相同的 SQL 语句执行之后返回的对象是共享的同一个,性能好,但是多线程并发可能会存在安全问题
    • false:读写缓存,多条相同的 SQL 语句执行之后返回的对象是副本,调用了 clone(),性能一般,但安全

MyBatis缓存查询的顺序

先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存。
如果一级缓存也没有命中,则查询数据库
SqlSession 关闭之后,一级缓存中的数据会写入二级缓存

四、MyBatis整合第三方缓存

4.1、MyBatis集成EhCache

  集成 EaChe 是为了代替 MyBatis 自带的二级缓存,一级缓存是无法被替代的。MyBatis 对外提供了接口,也可以集成第三方的缓存组件,比如 EhCache、Memcache 等。EhCache 是 Java 写的,Memcache 是 C语言 写的。因此,MyBatis 集成 EhCache 较为常见。

4.2、添加配置

<!-- Mybatis EHCache整合包 --> 
<dependency> 
    <groupId>org.mybatis.caches</groupId> 
    <artifactId>mybatis-ehcache</artifactId> 
    <version>1.2.1</version> 
</dependency> 

4.3、创建EHCache的配置文件

  创建 EHCache 的配置文件,配置文件名为 ehcache.xml。

<?xml version="1.0" encoding="utf-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!-- 磁盘保存路径 -->
    <diskStore path="E:\Program\Java\MyBatis\ehcache"/>
    <defaultCache
            maxElementsInMemory="1000"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

  EHCache配置文件说明

属性名 是否必须 作用
maxElementsInMemory 在内存中缓存的 element 的最大数目
maxElementsOnDisk 在磁盘上缓存的 element 的最大数目,若是 0 表示无穷大
eternal 设定缓存的elements是否永远不过期。
如果为true,则缓存的数据始终有效,
如果为false那么还要根据 timeToIdleSeconds、timeToLiveSeconds 判断
overflowToDisk 设定当内存缓存溢出的时候是否将过期的 element 缓存到磁盘上
timeToIdleSeconds 当缓存在 EhCache 中的数据前后两次访问的时间超过 timeToIdleSeconds 的属性取值时,
这些数据便会删除,默认值是 0,也就是可闲置时间无穷大
timeToLiveSeconds 缓存 element 的有效生命期,默认是 0,也就是 element 存活时间无穷大
diskSpoolBufferSizeMB DiskStore (磁盘缓存)的缓存区大小。默认是 30MB。
每个Cache都应该有自己的一个缓冲区
diskPersistent 在 VM 重启的时候是否启用磁盘保存EhCache中的数据,默认是 false。
diskExpiryThreadIntervalSeconds 磁盘缓存的清理线程运行间隔,默认是 120秒。
每个120s, 相应的线程会进行一次 EhCache 中数据的清理工作
memoryStoreEvictionPolicy 当内存缓存达到最大,有新的 element 加入的时候, 移除缓存中 element 的策略。
默认是 LRU(最近最少使用),可选的有 LFU(最不常使用)和 FIFO(先进先出)

4.4、设置二级缓存的类型

  在 MyBatis 的映射文件中设置二级缓存的类型。

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

标签:11,salary,缓存,age,empId,sex,MyBatis,public
From: https://www.cnblogs.com/nanoha/p/16812777.html

相关文章

  • 在旧版本centos上编译c++11的程序
    runac++programwithc++11supportinolderCentOSmachine从extras安装SoftwareCollections(SCL)yuminstallcentos-release-scl-rh安装devtoolset:yumin......
  • 911s5退出舞台后,哪个海外HTTP代理才是最佳的候补?
    在体育比赛中,往往在主力球员受伤或离队后,便会由替补球员候补登场完成比赛或是成为队里的新主力。而对于海外业务从业者最青睐的海外HTTP代理之一911s5来说,也是如此,前不久,911......
  • win11磁盘清理
    win11磁盘清理找不到了,网上说开启存储感知进行清理,但是打开之后,感知了半天也没有感知出来_(:з」∠)_解决方法win11磁盘清理可以搜索到,然后选择磁盘进行清理......
  • Springboot 项目普通类调用 Mapper 接口使用 MybatisPlus 报错:空指针异常(NullPointer
    Springboot项目普通类调用Mapper接口使用MybatisPlus报错:空指针异常(NullPointerException)报错开发时,在普通类调用Mapper接口使用MabatisPlus功能时会报出......
  • Linux清理缓存
     echo1>/proc/sys/vm/drop_caches//1释放页缓存echo2> /proc/sys/vm/drop_caches//2释放dentries和inodes缓存echo3> /proc/sys/vm/drop_caches......
  • MyBatis基础使用二
    MyBatis基础使用二配置Mybatis参考MyBatis基础用法一基本的CRUD接口UserMapperpackagecom.wfy.mapper;importcom.wfy.pojo.User;importorg.apache.ibatis.annota......
  • MyBatis基础使用四
    MyBatis基础使用四动态SQL一、多条件查询语句通过标签进行的多条件查询,通过test属性中的表达式判断标签中的内容是否有效(是否会拼接到sql中)<selectid="SelectCond......
  • SpringBoot+MybatisPlus--文件上传和下载实例
    文件上传时,file是一个临时文件,需要转存到指定位置,否则本次请求完成后临时文件就被删除Controller后台代码:packagecom.itheima.reggie.controller;importcom.itheima......
  • 11.ElasticSearch系列之搜索相关性算分机制
    1.相关性和相关性算分1.1相关性搜索的相关性算分,描述了一个文档和查询语句匹配的程度。ES会对每个匹配查询条件的结果进行算分_score打分的本质是排序,需要把最符合用......
  • CF1163F Indecisive Taxi Fee
    题意给定一张无向图,每次询问为更改一条边的边权后,从\(1\)到\(n\)的最短路。Solution首先考虑有哪些情况。如果原图中\(1\ton\)的最短路为路径\(P\),其上第\(i\)......