自定义mybatis插件实现读写分离

欢迎来到我的博客,代码的世界里,每一行都是一个故事


在这里插入图片描述

自定义mybatis插件实现读写分离

    • 前言
    • 场景分析
    • 前置配置讲解
    • 数据源切换实现
    • 代码实现(插件)
      • 说明
    • 注意
    • 实现效果

有时候我更想看到的是bug,比如做这个插件的时候

前言

在数据库的世界里,读写分离就像是一场神奇的变形术表演,能够让我们的应用程序更加稳定和高效。而MyBatis插件就像是一把神奇的魔杖,能够帮助我们实现数据库的读写分离。它就像是一位魔术师,能够在不同的数据库之间灵活切换,让我们的应用程序如虎添翼。现在,就让我们一起来揭开MyBatis插件的神秘面纱,探索它的魅力所在吧!

场景分析

要实现读写分离我们首先应该具备以下条件

1、多数据源场景,且可以动态切换数据源

2、在mybatis创建连接之前切换到想要的数据源

3、需要执行规则实现读写分离

大致就是上面的三点

前置配置讲解

# 数据源配置
spring.datasource.mysql.primary.url=jdbc:mysql://127.0.0.1:3361/base_sb?nullDatabaseMeansCurrent=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
spring.datasource.mysql.primary.username=root
spring.datasource.mysql.primary.password=123456
spring.datasource.mysql.primary.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源配置
spring.datasource.mysql.slave1.url=jdbc:mysql://127.0.0.1:3351/dingding_mid?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.mysql.slave1.username=root
spring.datasource.mysql.slave1.password=123456
spring.datasource.mysql.slave1.driver-class-name=com.mysql.cj.jdbc.Driver
@Bean(name = DataSourceType.PRIMARY)
@ConfigurationProperties(prefix = "spring.datasource.mysql.primary")
public DataSource primaryDataSource() {
  log.info("主数据库连接池创建中.......");
  return DruidDataSourceBuilder.create().build();
}

@Bean(name = DataSourceType.SECOND)
@ConfigurationProperties(prefix = "spring.datasource.mysql.slave1")
public DataSource secondDataSource() {
  log.info("second数据库连接池创建中.......");
  return DruidDataSourceBuilder.create().build();
}

上面是我的数据源,需要实现的就是当进行查询的时候我会走到slave1。

注意:我这里仅仅是为了展示效果,真正的读写分离是读库和写库一模一样,唯一的区别是读库read_only=1,也就是只读状态,并且他们的关系还是主从关系。

数据源切换实现

springboot整合多数据源的配置以及动态切换数据源,注解切换数据源

代码实现(插件)

package com.todoitbo.baseSpringbootDasmart.interceptor;

import com.todoitbo.baseSpringbootDasmart.multiDataSource.DataSourceContextHolder;
import com.todoitbo.baseSpringbootDasmart.multiDataSource.DataSourceType;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.util.Properties;

/**
 * @author xiaobo
 */
@Intercepts({
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Component
public class RoutingInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();

        // 使用MetaObject获取MappedStatement
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        while (metaObject.hasGetter("h")) {
            Object object = metaObject.getValue("h");
            metaObject = SystemMetaObject.forObject(object);
        }
        while (metaObject.hasGetter("target")) {
            Object object = metaObject.getValue("target");
            metaObject = SystemMetaObject.forObject(object);
        }
        // 通过反射获取到当前MappedStatement高版本没这个类了
        // MappedStatement mappedStatement = (MappedStatement) MetaObjectUtils.getFieldValue(statementHandler, "delegate.mappedStatement");
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        String sqlCommandType = mappedStatement.getSqlCommandType().toString();
        
        // 根据SQL命令类型,动态切换数据源
        if ("SELECT".equals(sqlCommandType)) {
            // 设置为数据库1的连接
            DataSourceContextHolder.setDataSource(DataSourceType.SECOND);
        } else {
            // 设置为数据库2的连接
            DataSourceContextHolder.setDataSource(DataSourceType.PRIMARY);
        }

        // 继续执行原有逻辑
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时,才包装目标类,否则直接返回
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
        // 这里可以接收到配置文件中的属性
    }
}

说明

当然,让我们逐一解析 RoutingInterceptor 类的主要方法:

  1. intercept(Invocation invocation): 这是拦截器的核心方法,当被拦截的方法(在本例中是 StatementHandlerprepare 方法)被调用时,这个方法会被执行。

    在这个方法中,首先获取了 StatementHandler 对象,然后通过 MetaObject 获取了 MappedStatement 对象。根据 MappedStatement 中的 sqlCommandType 判断当前执行的 SQL 是查询还是非查询,然后用 DataSourceContextHolder.setDataSource() 方法动态设置数据源,最后调用 invocation.proceed() 继续执行原有逻辑。

  2. plugin(Object target): 这个方法用于包装目标对象。当目标对象是 StatementHandler 类型时,使用 Plugin.wrap(target, this) 方法包装目标对象,这样当目标对象的方法被调用时,会先调用 intercept 方法。如果目标对象不是 StatementHandler 类型,直接返回目标对象。

  3. setProperties(Properties properties): 这个方法可以用于从配置文件中接收属性,但在这个拦截器中并未使用。

以上就是 RoutingInterceptor 类的主要方法。这个类实现了 MyBatis 的 Interceptor 接口,通过 @Intercepts@Signature 注解指定了要拦截的方法,然后在 intercept 方法中实现了动态数据源路由的逻辑。

注意

重点提一下需要注意的点

如果你除了这个拦截插件用到切换数据源之外还有别的,比如上面提到的数据源的切换,你定义了一个AOP,这个切点是service上,而你的这个service下又有数据库操作,那么这个很容易导致切换数据源失败

实现效果

image-20240423165428947

因为我的AOP干扰整整解决了1个多小时,弱弱的说自己一句好菜。终于等到了这个异常,也就是我的查询走的表是另一个库的表,而这个库并没有这个表。大公告成,完美收工

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

力扣HOT100 - 230. 二叉搜索树中第K小的元素

解题思路&#xff1a; class Solution {List<Integer> list new ArrayList<>();public int kthSmallest(TreeNode root, int k) {dfs(root);return list.get(k - 1);}public void dfs(TreeNode root) {if (root null) return;dfs(root.left);list.add(root.val)…

一个联合均值与方差模型的R包——dglm

目录 一、引言二、包的安装与载入三、模拟例子3.1 数据生成3.2 数据查看3.3 模型估计参数 一、引言 在 R 语言中&#xff0c;dglm 包是用于拟合双参数广义线性模型&#xff08;Double Generalized Linear Models&#xff0c;简称 DGLMs&#xff09;的一个工具。这类模型允许同…

使用STM32F103驱动读取MAX30102时的调试问题记录

1、数据FIFO寄存器&#xff0c;也就是地址为0x07的FIFO DATA寄存器&#xff0c;必须一次性将采样数据全部读出&#xff0c;也就是IIC起始信号发出后&#xff0c;必须连续读取FIFO中所有数据&#xff0c;如果一个字节一个字节读的话&#xff0c;实测FIFO的读写指针不会变化&…

YOLOv8改进项目汇总-超全改进-ultralyticsPro介绍:订阅了《芒果YOLOv8原创改进专栏》的读者免费赠送,包括很多稀有改进

&#x1f525;&#x1f525;&#x1f525;专注于YOLOv8改进&#xff0c;NEW - YOLOv8 &#x1f680; in PyTorch >, Support to improve Backbone, Neck, Head, Loss, IoU, LA, NMS and other modules&#x1f680; Makes YOLOv8 improvements easy again 芒果出品 YOLOv8…

HarmonyOS开发案例:【图片编辑】

介绍 本篇Codelab是基于ArkTS的声明式开发范式的样例&#xff0c;主要介绍了图片编辑实现过程。样例主要包含以下功能&#xff1a; 图片的解码。使用PixelMap进行图片编辑&#xff0c;如裁剪、旋转、亮度、透明度、饱和度等。图片的编码。 相关概念 [图片解码]&#xff1a;读…

NVIDIA Jetson jtop查看资源信息

sudo -H pip install -U jetson-stats 安装好之后可能需要reboot 执行jtop&#xff1a; 时间久了可能会退出&#xff0c;可参考如下再次启动。 nvidiategra-ubuntu:~$ jtop The jtop.service is not active. Please run: sudo systemctl restart jtop.service nvidiategra-ub…

3个比较不错的Linux云音乐应用程序整理

在现代音乐流媒体时代&#xff0c;基于云的音乐应用程序因其便利性和可访问性而变得非常流行。Linux 用户尤其寻求可靠且功能丰富的音乐播放器来无缝地享受他们喜爱的音乐。 在这里&#xff0c;我们探讨了三个最好的基于云的音乐应用程序&#xff0c;每个应用程序都提供专为 L…

Java技术学习|消息队列|初级RabbitMQ

学习材料声明 黑马RabbitMQ快速入门教程&#xff0c;快速掌握rabbitmq、springAMQP消息中间件 是非常初级的学习&#xff0c;听说后续的高级课程会涉及到微服务之类的&#xff0c;所以等学完微服务再回来学。还有redis的高级部分也涉及了微服务&#xff0c;所以也都暂时停止学…

OpenAI内部最受欢迎的10个GPTs推荐!建议收藏!

前面文章介绍了如何创建自己的GPTs&#xff0c;但是在应用商店里还有很多各种GPTs可以供我们使用&#xff0c;满足我们的各种应用场景。这就像手机的应用商店一样&#xff0c;我们除了可以自己编写应用程序之外&#xff0c;还可以使用使用应用商店里海量应用。有关创建自己的自…

YOLOv8 训练自己的数据集(20240423)

环境搭建请参考&#xff1a;Win10 搭建 YOLOv8 运行环境&#xff08;20240423&#xff09;-CSDN博客 环境测试请参考&#xff1a;本地运行测试 YOLOv8&#xff08;20240423&#xff09;-CSDN博客 一、使用 YOLOv8 的 coco128 数据集熟悉一下如何训练和预测 1.1、在项目根目录…

工厂提高OEE面临哪些挑战?

在工业生产中&#xff0c;提高设备的综合效率&#xff08;OEE&#xff09;是企业追求的目标之一。然而&#xff0c;实现高水平的OEE并非易事&#xff0c;工厂在这一过程中可能面临诸多挑战。本文将探讨工厂提高OEE所面临的挑战&#xff0c;并介绍PreMaint提供的解决方案。 提高…

【软件基础】反编译工具dnSpy反编译程序步骤

文章目录 一、dnSpy介绍二、使用版本三、使用步骤 一、dnSpy介绍 dnSpy是一款开源的.NET程序集反编译工具&#xff0c;它允许用户查看和编辑.NET程序集的源代码。dnSpy支持反编译.NET程序集、查看IL代码、编辑IL代码、调试.NET程序集等功能。用户可以使用dnSpy来分析和理解.NE…

【C语言__指针01__复习篇11】

目录 前言 一、什么是指针 二、计算机中常见的单位 三、CPU是怎样找到一块内存空间的 四、如何得到变量的地址 五、指针变量 六、解引用指针变量的作用 七、指针变量的大小 八、指针变量类型的意义 8.1 指针的解引用 8.2 指针-整数 九、void*指针 十、const修饰变…

国外问卷调查如何提高做题成功率?方法来了!

“为什么我做的题总是提交失败&#xff1f;” “做题太慢&#xff0c;收益太少&#xff0c;有什么做题技巧吗&#xff1f;” 以上这些问题&#xff0c;想必是新老玩家在问卷调查这条路上必定会遇到的&#xff0c;特别是新手遇到这类问题不知如何去处理&#xff0c;所以今天IPd…

el-popover放在el-table中点击无反应问题

我们想在table中给btn加弹框但是 el-popover点击按钮没有任何反应思考:通过插槽去添加这个组件el-popover的id是否绑定了一个值解决思路&#xff1a;给每个el-popover都加上单独的id 效果 &#xff1a; 代码 给每个组件都绑定ref <template slot-scope"scope"&g…

七星创客新零售系统:颠覆性商业模式的崛起

大家好&#xff0c;我是微三云周丽&#xff0c;今天给大家分析当下市场比较火爆的商业模式&#xff01; 小编今天跟大伙们分享什么是七星创客新零售系统&#xff1f; 随着经济的快速发展和科技的不断进步&#xff0c;商业模式的革新成为了企业发展的关键。在这个新旧动能转换、…

使用表格法插入公式和编号

如何将公式和编号优雅地插入到论文当中呢&#xff1f; 首先插入一个1行2列的表格 调整一下 输入公式方法一&#xff1a;感觉墨迹公式挺好用的&#xff0c;word自带的 输入公式方法二&#xff1a;图片转LATEX代码 这个方法更快 分享一个公式识别网站 图片识别得到LATEX代码&…

2024 初级信息处理技术员历史真题整理分享

2024 初级信息处理技术员历史真题整理分享 最近软考报名结束了&#xff0c;马上五月份就要考试&#xff0c;想必很多人都在迎战软考吧。 在此我分享一下我整理的一些软考&#xff08;初级信息处理技术员&#xff09;历史真题&#xff0c;供大家学习 历年真题 说明&#xff1a…

Windows本地部署Ollama+qwen本地大语言模型Web交互界面并实现公网访问

文章目录 前言1. 运行Ollama2. 安装Open WebUI2.1 在Windows系统安装Docker2.2 使用Docker部署Open WebUI 3. 安装内网穿透工具4. 创建固定公网地址 前言 本文主要介绍如何在Windows系统快速部署Ollama开源大语言模型运行工具&#xff0c;并安装Open WebUI结合cpolar内网穿透软…

汽车纵染压制专用液压机比例阀放大器

汽车纵染压制专用液压机比例阀放大器是一种专门用于汽车纵梁拉伸工艺的设备&#xff0c;它也可以用于其他金属薄板的压制成型及校正工艺。该类型的液压机通常具备独立的动力机构和电气系统&#xff0c;采用PLC技术进行控制&#xff0c;以确保操作的准确性和稳定性。除了纵梁拉伸…