0基础学习Mybatis系列数据库操作框架——自定义分布式缓存器

Mysql这类的数据库,其查询性能往往不能100%扛住我们业务请求量。于是我们一般都会在查询数据库之前,先查询下缓存。如果缓存存在,则直接使用缓存中数据;如果缓存失效,则读取数据库,并将数据记录到缓存中。
在这里插入图片描述
在这里插入图片描述
Mybatis有缓存机制,但是它只是本地缓存。在分布式环境下,这套机制就有很大的限制,于是本文我们将缓存内容保存在Redis上,这样部分于不同机器上的Mybatis都可以使用一个缓存库。
在这里插入图片描述

依赖

我们将使用Fastjson将Java对象序列化,然后保存在Redis中。

        <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.48</version></dependency>

Redis的连接库使用jedis

        <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>5.1.2</version></dependency>

缓存器类

缓存器需要实现org.apache.ibatis.cache.Cache。

package org.apache.ibatis.cache;import java.util.concurrent.locks.ReadWriteLock;public interface Cache {String getId();void putObject(Object key, Object value);Object getObject(Object key);Object removeObject(Object key);void clear();int getSize();default ReadWriteLock getReadWriteLock() {return null;}
}

我们主要要实现putObject和getObject方法。
当我们向数据库发送Select请求时,会调用getObject方法。在这个方法中,我们可以查询自己的缓存。如果缓存中查到了数据,就构造对象直接返回,这样Mybatis就不会查询数据库了,直接用了我们缓存的数据;如果缓存不存在,则该函数返回null。Mybatis就会访问数据库。

    public Object getObject(Object key) {try (Jedis jedis = pool.getResource()) {String cacheKey = genCacheKeyForRedis(key);if (jedis.exists(cacheKey)) {String jonsValue = jedis.get(cacheKey);List<JsonType> jsonTypeList = JSON.parseArray(jonsValue, JsonType.class);System.out.println(jonsValue);return jsonTypeList;}}return null;}

当数据库返回数据时,Mybatis会调用putObject通知我们将数据缓存起来。

    public void putObject(Object key, Object value) {try (Jedis jedis = pool.getResource()) {String cacheKey = genCacheKeyForRedis(key);String jonsValue = JSON.toJSONString(value);jedis.set(cacheKey, jonsValue);}}

上例中,pool是缓存器类的成员变量。它会在缓存器属性设置完毕后构造。这个调用过程也要借助Mybatis框架——缓存器类需要继承于org.apache.ibatis.builder.InitializingObject,并实现initialize方法。

public class JsonTypeCache implements Cache, InitializingObject {
……public void initialize() {pool = new JedisPool("localhost", 6379);}
……

完整代码如下

package org.example.cache;import com.alibaba.fastjson2.JSON;
import org.apache.ibatis.builder.InitializingObject;
import org.apache.ibatis.cache.Cache;
import org.apache.ibatis.cache.CacheKey;
import org.example.model.JsonType;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;import java.util.List;public class JsonTypeCache implements Cache, InitializingObject {private JedisPool pool;private final String id;public JsonTypeCache(String id) {this.id = id;}public void initialize() {pool = new JedisPool("localhost", 6379);}public String getId() {return id;}private String genCacheKeyForRedis(Object key) {CacheKey cacheKey = (CacheKey) key;return cacheKey.toString();}public void putObject(Object key, Object value) {try (Jedis jedis = pool.getResource()) {String cacheKey = genCacheKeyForRedis(key);String jonsValue = JSON.toJSONString(value);jedis.set(cacheKey, jonsValue);}}public Object getObject(Object key) {try (Jedis jedis = pool.getResource()) {String cacheKey = genCacheKeyForRedis(key);if (jedis.exists(cacheKey)) {String jonsValue = jedis.get(cacheKey);List<JsonType> jsonTypeList = JSON.parseArray(jonsValue, JsonType.class);System.out.println(jonsValue);return jsonTypeList;}}return null;}public Object removeObject(Object key) {System.out.println("removeObject");return null;}public void clear() {System.out.println("clear");}public int getSize() {return 0;}
}

需要注意的是genCacheKeyForRedis方法,它用于生成Redis的key。本例中的实现直接用了CacheKey的序列化方法。在分布式环境下,这个方法是否可以针对相同SQL和参数生成相同Key值,是需要进一步验证的。

配置

在SQL Mapper XML中新增如下项

    <cache type="org.example.cache.JsonTypeCache"/>

对于需要使用Cache的Select语句,新增useCache属性

    <select id="selectJsonTypeElems" resultMap="jsonTypeResultMap" useCache = "true">select * from all_type where info_int = #{intInfo}</select>

Update、Delete、Insert这类操作都会导致数据库变动,进而会影响Select的结果。这样缓存就会与数据库中数据不一致。一种办法是给这类语句加上flushCache属性,这样这些指令调用时,会调用缓存器的clear方法(本例中我们给这个方法填充有意义的操作)。我们可以在这个方法中删除所有缓存。这个方法的粒度太大了,所以并不推荐。一种更好的方法是借用后面介绍的拦截器,有针对性的清除缓存,而不是清除所有缓存。

    <delete id="deleteJsonTypeElems" flushCache = "true">delete from all_type where info_int = #{intInfo}</delete>

完整XML见下

<?xml version="1.0" encoding="UTF-8"?>
<!-- AllTypeMapperJsonCache.xml -->
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.AllTypeMapper"><parameterMap id="jsonTypeParameterMap" type="JsonType"><parameter property="jsonElemList" jdbcType="LONGVARCHAR" typeHandler="org.example.typehandlers.JsonListHandler"/></parameterMap><resultMap id="jsonTypeResultMap" type="JsonType"><result property="intInfo" column="info_int"/><result property="jsonElemList" column="info_ltext" jdbcType="LONGVARCHAR" typeHandler="org.example.typehandlers.JsonListHandler"/></resultMap><insert id="insertJsonTypeElems" flushCache = "true">insert into all_type(info_int, info_ltext) values<foreach item="item" collection="list" separator=",">(#{item.intInfo}, #{item.jsonElemList, typeHandler=org.example.typehandlers.JsonListHandler})</foreach></insert><update id="updateJsonTypeElems" flushCache = "true">update all_type set info_ltext  = #{jsonElemList, jdbcType=LONGVARCHAR} where info_int = #{intInfo}</update><cache type="org.example.cache.JsonTypeCache"/><select id="selectJsonTypeElems" resultMap="jsonTypeResultMap" useCache = "true">select * from all_type where info_int = #{intInfo}</select><delete id="deleteJsonTypeElems" flushCache = "true">delete from all_type where info_int = #{intInfo}</delete>
</mapper>

测试

package org.example;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 org.example.mapper.AllTypeMapper;
import org.example.model.AllType;
import org.example.model.JsonType;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;public class CacheTest {private static SqlSessionFactory sqlSF;@BeforeAllstatic void CreateSessionFactory() throws IOException {InputStream in = Resources.getResourceAsStream("mybatis/config/mybatis-config-json-cache.xml");sqlSF = new SqlSessionFactoryBuilder().build(in);}@Testvoid testUpdate() {try (SqlSession s = sqlSF.openSession(true)) {AllTypeMapper all_type_mapper = s.getMapper(AllTypeMapper.class);JsonType a = new JsonType();a.setIntInfo(1);List<JsonType.JsonElem> jsonElemList = Arrays.asList(new JsonType.JsonElem(1,"1"),new JsonType.JsonElem(2,"2"));JsonType.JsonList jsonList = new JsonType.JsonList(jsonElemList);a.setJsonElemList(jsonList);long count = all_type_mapper.updateJsonTypeElems(a);System.out.println(count);}}@Testvoid testSelect() {try (SqlSession s = sqlSF.openSession(true)) {AllTypeMapper all_type_mapper = s.getMapper(AllTypeMapper.class);List<JsonType> all = all_type_mapper.selectJsonTypeElems(100);for (JsonType a : Objects.requireNonNull(all)) {JsonType.JsonList jsonList = a.getJsonElemList();if (null == jsonList) {continue;}for (JsonType.JsonElem b: jsonList.getJsonElemList()) {System.out.printf("%d %s\n", b.getFirst(), b.getSecond());}}}}@Testvoid testinsertJsonTypeElems() {List<JsonType> jsonTypeList = new ArrayList<>();for (int i = 0; i < 10; i++) {JsonType a = new JsonType();a.setIntInfo(i+100);List<JsonType.JsonElem> jsonElemList = Arrays.asList(new JsonType.JsonElem(i+1100, "1"),new JsonType.JsonElem(i+1200, "2"));JsonType.JsonList jsonList = new JsonType.JsonList(jsonElemList);a.setJsonElemList(jsonList);jsonTypeList.add(a);}try (SqlSession s = sqlSF.openSession(true)) {AllTypeMapper all_type_mapper = s.getMapper(AllTypeMapper.class);long count = all_type_mapper.insertJsonTypeElems(jsonTypeList);System.out.println(count);}}@Testvoid testDelete() {try (SqlSession s = sqlSF.openSession(true)) {AllTypeMapper all_type_mapper = s.getMapper(AllTypeMapper.class);long count = all_type_mapper.deleteJsonTypeElems(110);System.out.println(count);}}
}

总结

  • 继承org.apache.ibatis.cache.Cache接口,主要实现putObject和getObject方法。
  • getObject返回null时,Mybatis会查询数据库;getObject返回对象时,Mybatis直接返回该对象,而不会查询数据库。
  • 当Mybatis查询数据库后,会调用putObject方法,让我们有保存数据到缓存的机会。
  • 实现org.apache.ibatis.builder.InitializingObject接口,让缓存器在构造时有我们自定义的初始化的机会。
  • 需要在SQL Mapper XML中新增标签,告知Mybatis这个Mapper中的缓存器是哪个。
  • 缓存器从属于Mapper,不同Mapper可以设置不同缓存器。
  • SQL Mapper XML中Select语句使用useCache = "true"表达这个SQL使用缓存器。
  • SQL Mapper XML中Update、Delete、Insert语句使用flushCache = "true"语句该SQL使用了缓存器,它的效果就是调用缓存器的clear方法。该方法没有任何参数,只能全部清空缓存。

代码样例见:https://github.com/f304646673/mybatis_demo.git

参考资料

  • https://mybatis.org/mybatis-3/zh_CN/sqlmap-xml.html#cache

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

手动实现Tomcat底层机制+自己设计Servlet

文章目录 1.Tomcat整体架构分析自己理解 2.第一阶段1.实现功能2.代码1.TomcatV1.java 3.调试阶段1.阻塞在readLine导致无法返回结果 4.结果演示 3.第二阶段1.实现功能2.代码1.RequestHander.java2.TomcatV2.java 3.调试阶段1.发现每次按回车会接受到两次请求 4.结果演示 4.第三…

【服务器部署篇】Linux下安装Docker容器

作者介绍&#xff1a;本人笔名姑苏老陈&#xff0c;从事JAVA开发工作十多年了&#xff0c;带过大学刚毕业的实习生&#xff0c;也带过技术团队。最近有个朋友的表弟&#xff0c;马上要大学毕业了&#xff0c;想从事JAVA开发工作&#xff0c;但不知道从何处入手。于是&#xff0…

【自用笔记】【大数据】

1 mapreduce &#xff08;1&#xff09;Map任务的数量&#xff1a;由输入数据的大小决定的&#xff0c;如文件数量和大小、HDFS块大小以及FileInputFormat的设置等。每个MapSlot可以运行一个Map任务 &#xff08;2&#xff09;Reduce任务的数量&#xff08;分区数&#xff09;&…

配置Mysql集群主从复制报错

配置Mysql集群主从复制报错: 我在master创建了一个数据库&#xff08;demo_ds_0&#xff09;,然后又快速删除了。然后又再次创建了 demo_ds_0, 发现 slave 数据库没有将 demo_ds_0 库同步过来。 后面在 slave 数据库中执行 show slave status 发现 SLave_IO_Running 字段是 …

练习 17 Web [极客大挑战 2019]PHP

常见的网站源码备份文件名和后缀&#xff0c;反序列化攻击 unserialize()&#xff1a;wakeup绕过&#xff0c;private类以及属性序列化后的%00修改 开靶机 提到”备份“ 那看看有没有backup.php啥的 如果网站存在备份文件&#xff0c;常见的备份文件后缀名有&#xff1a;“.gi…

开源代码分享(17)-基于足球队训练算法(Football Team Training Algorithm,FTTA)的组合风速预测

参考文献&#xff1a; [1]Tian Z, Gai M. Football team training algorithm: A novel sport-inspired meta-heuristic optimization algorithm for global optimization[J]. Expert Systems with Applications, 2024, 245: 123088. 1.算法基本原理 足球队训练算法&#xff0…

练习14 Web [极客大挑战 2019]Upload

phtml格式绕过&#xff0c;burp修改content-type绕过&#xff0c;常见的文件上传存放目录名 题目就叫upload&#xff0c;打开靶机 直接上传一个图片格式的一句话木马&#xff0c;返回如下&#xff1a; 提交练习5和9中的两种可以执行图片格式php代码的文件&#xff0c;修改con…

前端三剑客 —— CSS (第四节)

目录 内容回顾&#xff1a; 1.常见样式 2.特殊样式 特殊样式 过滤效果 动画效果 动画案例&#xff1a; 渐变效果 其他效果&#xff1a; 多列效果 字体图标&#xff08;icon&#xff09; 内容回顾&#xff1a; 1.常见样式 text-shadow x轴 y轴 阴影的模糊程度 阴影的…

蓝桥杯-冶炼金属(二分求最大最小)

P9240 [蓝桥杯 2023 省 B] 冶炼金属 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 二分做法&#xff1a; #include<bits/stdc.h> using namespace std; #define int long long const int N 1e410; int n,a,b; int v[N],cnt[N]; int check(int x){for(int i1;i<n;i…

JS详解-设计模式

工厂模式&#xff1a; 单例模式&#xff1a; // 1、定义一个类class SingleTon{// 2、添加私有静态属性static #instance// 3、添加静态方法static getInstance(){// 4、判断实例是否存在if(!this.#instance){// 5、实例不存在&#xff0c;创建实例this.#instance new Single…

[AIGC] SpringMVC, Filter, Interceptor之间关系详解

在Web应用开发过程中&#xff0c;我们经常需要处理各样的HTTP请求和响应。在Spring框架中&#xff0c;我们主要借助SpringMVC、Filter和Interceptor来处理这些任务。那么&#xff0c;这三者之间有什么关系&#xff0c;又分别扮演着什么角色呢&#xff1f;本文将带你一探究竟。 …

VS Code 配置 cmake

CMake 用法参考 CMake学习 cmake使用详细教程&#xff08;日常使用这一篇就足够了&#xff09;_cmake教程-CSDN博客 手动添加 CMake 编译器的搜索路径 如果没有设置上面的路径&#xff0c;有些编译器是找不到的 指定cmake 生成文件的路径 生成makefile 等文件的路径 可以指…