MyBatis中三级缓存的理解

news2025/1/17 15:47:21

文章目录

  • 前言
  • 1. 环境搭建
    • 1.1 依赖引入
    • 1.2 mybatis-config.xml配置
      • 配置db.properties
      • 在mybatis-config.xml引入db.properties
    • 1.3 实体类
    • 1.4 mapper
      • mapper接口
      • mapper映射文件
    • 1.5 测试
  • 2.缓存
    • 2.1 一级缓存
      • mybatis-config.xml 配置日志
      • 开启日志
      • 配置日志文件logback.xml
      • 测试
      • 相同mapper,相同参数
      • 相同mapper,不同参数
    • 2.2 二级缓存
      • mybatis-config.xml配置文件开启缓存配置
      • 实体类实现序列化
      • mapper的xml配置缓存
      • 测试代码
      • 测试,缓存命中率只有0.5
    • 2.3 三级缓存
      • 引入缓存的依赖
      • 配置文件redis.properties
      • 自定义缓存实现cache接口
      • 配置mapper xml配置
      • 测试
      • redis缓存数据
      • 第二次再次执行查询命中率1.0

前言

缓存的作用:不加缓存 1。查询的效率降低 2。增大了数据库的压力

  • 一级缓存:会话级别的
  • 二级缓存:进程级别的
  • 三级缓存:自定义缓存

项目中要使用缓存 全局配置文件中 settings 我们需要打开, 在对应的映射文件中 <cache>一级缓存是默认使用的。二级缓存我们需要自己开启:一级和二级缓存的执行的先后顺序:先查二级缓存。二级缓存没有进行一级缓存。一级缓存如果还是没有那么走数据库查询
作用域:一级缓存的作用域是session级别的,命中率很低。

1. 环境搭建

1.1 依赖引入

 <!--引入mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.13</version>
    </dependency>
    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.22</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

1.2 mybatis-config.xml配置

配置db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://ip:3306/mybatisplus?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
username=root
password=123456

在mybatis-config.xml引入db.properties

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!---导入配置文件-->
    <properties resource="db.properties"></properties>
    <environments default="development">
        <environment id="development">
              <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/PersonMapper.xml"/>
    </mappers>
</configuration>

1.3 实体类

省略set get方法

/**
 * person实体类
 */
public class Person {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}

1.4 mapper

mapper接口

/**
 * 查询患者
 */
public interface PersonMapper {
    /**
     * 根据id查询人员信息
     * @param id
     * @return
     */
    Person selectPerson(long id);
}

mapper映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.elite.mybatis.mapper.PersonMapper">
    <!--查询人员信息-->
    <select id="selectPerson" resultType="com.elite.mybatis.entity.Person">
        select * from person where id = #{id}
    </select>
</mapper>

1.5 测试

 /**
     * 查询人员信息
     */
    @Test
    public void testSelect() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Person person = sqlSession.selectOne("com.elite.mybatis.mapper.PersonMapper.selectPerson", 1L);
        System.out.println(person.toString());
        ///Person{id=1, name='elite', age=22, email='elite@qq.com'}
    }

项目结构:
项目结构

2.缓存

2.1 一级缓存

一级缓存默认是开启的,作用域是SqlSession 级别的,所以命中率极低。相同SqlSession ,多次调用同一个Mapper和同一个方法的同一个参数,只会进行一次数据库查询,然后把数据缓存到缓冲中,以后直接先从缓存中取出数据,不会直接去查数据库。

mybatis-config.xml 配置日志

 <!--设置日志实现-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

开启日志

    <!--加入日志-->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.4.2</version>

配置日志文件logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>
   <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>%5level [%thread] - %msg%n</pattern>
        </encoder>
    </appender>

    <logger name="com.elite.mybatis.mapper.PersonMapper">
        <level value="trace"/>
        <Root level="info" >
            <AppenderRef ref="stdout"/>
        </Root>
    </logger>
</configuration>

测试

    /**
     * 查询人员信息
     */
    @Test
    public void testSelect() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        System.out.println("==========第一次调用========================");
        Person person = sqlSession.selectOne("com.elite.mybatis.mapper.PersonMapper.selectPerson", 1L);
        System.out.println(person.toString());
        System.out.println("==========第二次调用========================");
        Person person1 = sqlSession.selectOne("com.elite.mybatis.mapper.PersonMapper.selectPerson", 1L);
        System.out.println(person1.toString());
    }

相同mapper,相同参数

相同参数

相同mapper,不同参数

不同参数

2.2 二级缓存

二级缓存:
是多个 SqlSession 共享的,其作用域是 mapper 的同一个 namespace,不同 的 sqlSession 两次执行相同 namespace 下的 sql 语句且向 sql 中传递参数也相同即最终执行 相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从 缓存中获取数据将不再从数据库查询,从而提高查询效率。Mybatis 默认没有开启二级缓存 需要在 setting 全局参数中配置开启二级缓存。

mybatis-config.xml配置文件开启缓存配置

  <settings>
        <!--设置日志实现-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!--开启二级缓存-->
        <setting name="cacheEnabled" value="true"/>
    </settings>

实体类实现序列化

public class Person implements Serializable {
}

mapper的xml配置缓存

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.elite.mybatis.mapper.PersonMapper">

      <!--eviction: 清空缓存的策略
        readOnly: 是否只读
        flushInterval: 每个60秒刷新一次缓存
        size: 内存大小,最多存储结果对象或者列表的512个引用 -->
    <cache readOnly="true" eviction="FIFO" flushInterval="60000" size="512"/>
   
    <!--查询人员信息-->
    <select id="selectPerson" resultType="com.elite.mybatis.entity.Person" flushCache="false" useCache="true" >
        select * from person where id = #{id}
    </select>
</mapper>

测试代码

    /**
     * 查询人员信息
     */
    @Test
    public void testSelect1() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        System.out.println("==========第一个sqlSession="+sqlSession.hashCode()+"========================");
        PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
        System.out.println( personMapper.selectPerson(1L).toString());
        sqlSession.close();
        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        System.out.println("==========第二个sqlSession="+sqlSession1.hashCode()+"========================");
        PersonMapper personMapper1 = sqlSession1.getMapper(PersonMapper.class);
        System.out.println(personMapper1.selectPerson(1L).toString());
        sqlSession1.close();
    }

测试,缓存命中率只有0.5

==========第一个sqlSession=236230========================
Cache Hit Ratio [com.elite.mybatis.mapper.PersonMapper]: 0.0
Opening JDBC Connection
Created connection 17500244.
==>  Preparing: select * from person where id = ?
==> Parameters: 1(Long)
<==    Columns: id, name, age, email
<==        Row: 1, elite, 22, elite@qq.com
<==      Total: 1
Person{id=1, name='elite', age=22, email='elite@qq.com'}
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@10b0854]
Returned connection 17500244 to pool.
==========第二个sqlSession=29457283========================
Cache Hit Ratio [com.elite.mybatis.mapper.PersonMapper]: 0.5
Opening JDBC Connection
Checked out connection 17500244 from pool.
==>  Preparing: select * from person where id = ?
==> Parameters: 1(Long)
<==    Columns: id, name, age, email
<==        Row: 1, elite, 22, elite@qq.com
<==      Total: 1
Person{id=1, name='elite', age=22, email='elite@qq.com'}
Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@10b0854]
Returned connection 17500244 to pool.

2.3 三级缓存

由于一级缓存二级缓存的命中率极低,都是在单个进程之类进行缓存,多进程缓存就不好使,mybatis默认提供了接口可以自定义缓存。
此处我们整合redis进行缓存。

引入缓存的依赖

  <!--mybatis-redis缓存依赖-->
    <dependency>
      <groupId>org.mybatis.caches</groupId>
      <artifactId>mybatis-redis</artifactId>
      <version>1.0.0-beta2</version>
    </dependency>

配置文件redis.properties

host=redis ip
port=6379
connectionTimeout=500000
password=
database=0

自定义缓存实现cache接口

package com.elite.mybatis.cache;

import org.apache.ibatis.cache.Cache;
import org.mybatis.caches.redis.*;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 自定义com.elite.mybatis.cache.MyRedisCache;
 */
public class MyRedisCache implements Cache {
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private String id;
    private static JedisPool pool;

    public MyRedisCache(String id) throws IOException {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        } else {
            this.id = id;
            Properties properties = new Properties();
            // 使用ClassLoader加载properties配置文件生成对应的输入流
            InputStream in = MyRedisCache.class.getClassLoader().getResourceAsStream("redis.properties");
            // 使用properties对象加载输入流
            properties.load(in);
             //获取key对应的value值
            String host = properties.getProperty("host");
            String port = properties.getProperty("port");
            in.close();
            RedisConfig redisConfig =new RedisConfig();
            pool = new JedisPool(redisConfig,host,Integer.valueOf(port), 5000, 5000,null,0,"mybatis-redis" );
        }
    }

    private Object execute(RedisCallback callback) {
        Jedis jedis = pool.getResource();

        Object var3;
        try {
            var3 = callback.doWithRedis(jedis);
        } finally {
            jedis.close();
        }

        return var3;
    }

    public String getId() {
        return this.id;
    }

    public int getSize() {
        return (Integer)this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                Map<byte[], byte[]> result = jedis.hgetAll(MyRedisCache.this.id.toString().getBytes());
                return result.size();
            }
        });
    }

    public void putObject(final Object key, final Object value) {
        this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                jedis.hset(MyRedisCache.this.id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
                return null;
            }
        });
    }

    public Object getObject(final Object key) {
        return this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                return SerializeUtil.unserialize(jedis.hget(MyRedisCache.this.id.toString().getBytes(), key.toString().getBytes()));
            }
        });
    }

    public Object removeObject(final Object key) {
        return this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                return jedis.hdel(MyRedisCache.this.id.toString(), new String[]{key.toString()});
            }
        });
    }

    public void clear() {
        this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                jedis.del(MyRedisCache.this.id.toString());
                return null;
            }
        });
    }

    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }

    public String toString() {
        return "Redis {" + this.id + "}";
    }
}

配置mapper xml配置

<!--设置三级缓存类地址-->
<cache type="com.elite.mybatis.cache.MyRedisCache" />

测试

 /**
     * 查询人员信息
     */
    @Test
    public void testSelect3() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
        System.out.println( personMapper.selectPerson(1L).toString());
        sqlSession.close();
    }

redis缓存数据

查询缓存

第二次再次执行查询命中率1.0

缓存

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/550645.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习之搭建LSTM模型预测股价

大家好&#xff0c;我是带我去滑雪&#xff01; 本期利用Google股价数据集&#xff0c;该数据集中GOOG_Stock_Price_Train.csv为训练集&#xff0c;GOOG_Stock_Price_Test.csv为测试集&#xff0c;里面有开盘价、最高股价、最低股价、收盘价、调整后的收盘价、成交量&#xff0…

Flutter项目webview加载没有HTTPS证书的网页在Android和iOS设备上无法显示的解决方案

一、问题描述 Flutter项目使用谷歌官方webview库 webview_flutter&#xff0c;加载自签名证书、证书失效、无证书等HTTPS网页地址时&#xff0c;在Android或pc浏览器中提示证书失效&#xff0c;在iOS设备上为空白页&#xff0c;为了加载自签名证书的网页&#xff0c;需要饶过i…

AVR单片机ATemga328P中断原理的介绍

1、一AVR单片机中断原理的介绍 ATmega328P微控制器具有两个外部中断引脚&#xff0c;分别是INT0和INT1。 外部中断0&#xff08;INT0&#xff09;&#xff1a;它对应的引脚是PD2&#xff08;数字引脚2&#xff09;。INT0可以用于响应外部信号的边沿触发&#xff08;上升沿、下…

【服务器】使用Nodejs搭建HTTP web服务器

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员&#xff0c;2024届电子信息研究生 目录 前言 1.安装Node.js环境 2.创建node.js服务 3. 访问node.js 服务 4.内网穿透 4.1 安装配置cpolar内网穿透 4.2 创建隧道映射本地端口 5.固定公网地址 [TOC] 转载自内网穿透…

Unity Addressables学习笔记(1)---创建远程服务器加载资源

例子1&#xff1a;加载一个图片 1.首先创建一个UI Image&#xff0c;空白图片,资源打包方式选择真是部署的 2.修改远程发布和加载配置 Bulid Path选择RemoteBuildPath Load Path我选择了custom,地址是http://localhost:8080/WebGL/ 遇坑1 :最开始我选择的Build Path 是 Loca…

windows安装mysql 5.7.41

前言 要学mysql&#xff0c;肯定得本地装上一个玩一玩啦&#xff0c;下面一起来安装mysql吧 一、下载 https://downloads.mysql.com/archives/community/ 顺便说一下&#xff0c;下载按钮下方有个md5&#xff0c;可以验证下文件是否被篡改&#xff0c;理论上官网下载的应该问…

初识结构体

目录 结构体的声明 结构体的基础知识 结构体的声明 结构体成员的类型 结构体变量的定义和初始化 定义 初始化 结构体成员的访问 结构体变量访问成员 结构体指针访问指向变量的成员 结构体传参 传地址 传结构体 结论 结构体的声明 结构体的基础知识 数组&#xff…

【ChatGPT】IOS如何下载注册使用ChatGPT的APP(教学)

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化 &#x1f449;专__注&#x1f448;&#xff1a;专注主流机器人、人工智能等相关领域的开发、…

iptables 防火墙

iptables概述 Linux系统的防火墙&#xff1a;ip信息过滤系统&#xff0c;它实际上由两个组件netfilter和iptables组成。 主要工作在网络层&#xff0c;针对IP数据包。体现在对包内的IP地址、端口、协议等信息的处理上 netfilter / iptables关系&#xff1a; netfilter:属于…

Electron中如何创建模态窗口?

目录 前言一、模态窗口1.Web页面模态框2.Electron中的模态窗口3.区分父子窗口与模态窗口 二、实际案例使用总结 前言 模态框是一种常用的交互元素&#xff0c;无论是在 Web 网站、桌面应用还是移动 APP 中&#xff0c;都有其应用场景。模态框指的是一种弹出窗口&#xff0c;它…

【TES714】JFM7K325T(复旦微FPGA)+HI3531DV200(华为海思)的综合视频处理平台设计原理图及调试经验

板卡概述 TES714是自主研制的一款5路HD-SDI视频采集图像处理平台&#xff0c;该平台采用上海复旦微的高性能Kintex系列FPGA加上华为海思的高性能视频处理器HI3531DV200来实现。 华为海思的HI3531DV200是一款集成了ARM A53四核处理器性能强大的神经网络引擎&#xff0c;支持多种…

【运维知识进阶篇】集群架构-Nginx动静分离详解

我们先前将静态资源放到NFS&#xff0c;动态资源放到MySQL&#xff0c;一是为了提高我们Web服务器性能&#xff0c;减轻它的压力&#xff0c;另一面如果Web宕机了&#xff0c;我们的静态和动态资源还可以访问到。但是之前方式不管是静态还是动态文件&#xff0c;都是走的代码文…

ssl vpn 与 ipsec vpn 区别

VPN 安全协议有两种主要类型&#xff0c;IPsec 和 SSL&#xff0c;了解它们之间的区别对于确保客户的安全至关重要。在本文中&#xff0c;我们将解释IPsec 和 SSL VPN 协议之间的区别&#xff0c;以及如何选择合适的协议来满足客户的需求。了解更多SSL技术最新信息&#xff0c;…

Linux_证书_Openssl实现对称加密、非对称加密、CA颁布证书

文章目录 OpenSSLopenssl实现对称加密openssl实现非对称加密生成密钥对非对称加密数字签名小结 根据CA颁布证书生成ca私钥和ca证书根据ca生成证书 尾声 OpenSSL 常用证书生成工具包括三个&#xff1a;ssh-keygen、cfssl、openssl。这里介绍 OpenSSL , OpenSSL 是一个开源项目&…

【Python从入门到进阶】20、HTML页面结构的介绍

接上篇《19、Python异常处理》 上一篇我们学习了Python中有关异常&#xff08;捕获异常、处理异常等&#xff09;的知识。从本篇开始&#xff0c;我们进入Python的实战教程&#xff0c;学习爬虫的相关技术&#xff0c;本篇主要讲解要爬取的HTML页面的结构。 一、一个场景 假设…

Godot引擎 4.0 文档 - 入门介绍 - Godot 编辑器

本文为Google Translate英译中结果&#xff0c;DrGraph在此基础上加了一些校正。英文原版页面&#xff1a; First look at Godots editor — Godot Engine (stable) documentation in English Godot的编辑器 本页将为您简要介绍 Godot 的界面。我们将查看不同的主屏幕和停靠栏…

C语言:字符函数和字符串函数详解及部分函数的模拟实现(前篇)

文章目录 求字符串长度strlenstrlen函数的模拟实现: 长度不受限制的字符串函数strcpystrcatstrcmp总结 长度受限制的字符串函数介绍strncpystrncatstrncmp 前言&#xff1a; C语言中对字符和字符串的处理很是频繁&#xff0c;但是C语言本身是没有字符串类型的&#xff0c;字符串…

【LeetCode】382. 链表随机节点

382. 链表随机节点&#xff08;中等&#xff09; 方法一 思路 定义两个链表&#xff0c;一个origin&#xff0c;用于每次调用 getRandom() 时进行初始化&#xff0c;一个 l 用于每次调用 getRandom() 时进行遍历&#xff0c;找到随机选定的元素。首先在 Solution() 的时候&am…

SpringBoot原理——起步依赖与自动装配

文章目录 SpringBoot原理一、起步依赖二、自动配置2.1 概述2.2 工具类准备工作2.2.2 HeaderConfig2.2.3 HeaderGenerator2.2.4 HeaderParser2.2.5 MyImportSelector2.2.6 TokenParser2.2.7 pom.xml文件 2.3 自动配置原理2.3.1 引入工具类2.3.2 案例 &#xff1a; 访问第三方Bea…

GPT专业应用:撰写工作简报

●图片由Lexica 生成&#xff0c;输入&#xff1a;Workers working overtime 工作简报&#xff0c;作为一种了解情况、沟通信息的有效手段&#xff0c;能使上级机关和领导及时了解、掌握所属部门的政治学习、军事训练、行政管理等方面的最新情况&#xff1b;同时&#xff0c;能…