SpringBoot+MybatisPlus实现读写分离,自动切换数据源

读写分离有必要吗?

实现读写分离势必要与你所做的项目相关,如果项目读多写少,那就可以设置读写分离,让“读”可以更快,因为你可以把你的“读”数据库的innodb设置为MyISAM引擎,让MySQL处理速度更快。

实现读写分离的步骤

监听MybatisPlus接口,判断是写入还是读取

在这里我使用的是AOP的方式,动态监听MybatisPlus中Mapper的方法。

import com.supostacks.wrdbrouter.DBContextHolder;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect
@Component
public class MyBatisPlusAop {@Pointcut("execution(* com.baomidou.mybatisplus.core.mapper.BaseMapper.select*(..))")public void readPointCut(){}@Pointcut("execution(* com.baomidou.mybatisplus.core.mapper.BaseMapper.insert*(..))" +"||execution(* com.baomidou.mybatisplus.core.mapper.BaseMapper.update*(..))" +"||execution(* com.baomidou.mybatisplus.core.mapper.BaseMapper.delete*(..))")public void writePointCut(){}@Before("readPointCut()")public void readBefore(){DBContextHolder.setDBKey("dataread");}@Before("writePointCut()")public void writeBefore(){DBContextHolder.setDBKey("datawrite");}
}

定义介绍:
DBContextHolder中使用了ThreadLocal存储数据库名
readPointCut定义读的切点,如果调用的是BaseMapper.select*(…)则判断是读数据,则调用读库。
writePointCut定义写的切点,如果调用的是BaseMapper.insert|update|delete*(…)则判断是写数据,则调用写库

自定义MyBatis的DataSourceAutoConfiguration

DataSourceAutoConfiguration是Mybatis官方使用的SpringBootStarter,因为我这边自定义了Mybatis连接的相关属性名用来切换数据源,所以我需要自构一个DataSourceAutoConfig,代码如下:


@Configuration
public class DataSourceAutoConfig implements EnvironmentAware {private final String TAG_GLOBAL = "global";/*** 数据源配置组*/private final Map<String, Map<String, Object>> dataSourceMap = new HashMap<>();/*** 默认数据源配置*/private Map<String, Object> defaultDataSourceConfig;public DataSource createDataSource(Map<String,Object> attributes){try {DataSourceProperties dataSourceProperties = new DataSourceProperties();dataSourceProperties.setUrl(attributes.get("url").toString());dataSourceProperties.setUsername(attributes.get("username").toString());dataSourceProperties.setPassword(attributes.get("password").toString());String driverClassName = attributes.get("driver-class-name") == null ? "com.zaxxer.hikari.HikariDataSource" : attributes.get("driver-class-name").toString();dataSourceProperties.setDriverClassName(driverClassName);String typeClassName = attributes.get("type-class-name") == null ? "com.zaxxer.hikari.HikariDataSource" : attributes.get("type-class-name").toString();return dataSourceProperties.initializeDataSourceBuilder().type((Class<DataSource>) Class.forName(typeClassName)).build();} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}@Beanpublic DataSource createDataSource() {// 创建数据源Map<Object, Object> targetDataSources = new HashMap<>();for (String dbInfo : dataSourceMap.keySet()) {Map<String, Object> objMap = dataSourceMap.get(dbInfo);// 根据objMap创建DataSourceProperties,遍历objMap根据属性反射创建DataSourcePropertiesDataSource ds = createDataSource(objMap);targetDataSources.put(dbInfo, ds);}// 设置数据源DynamicDataSource dynamicDataSource = new DynamicDataSource();dynamicDataSource.setTargetDataSources(targetDataSources);// db0为默认数据源dynamicDataSource.setDefaultTargetDataSource(createDataSource(defaultDataSourceConfig));return dynamicDataSource;}@Overridepublic void setEnvironment(Environment environment) {String prefix = "wr-db-router.spring.datasource.";String datasource = environment.getProperty(prefix + "db");Map<String, Object> globalInfo = getGlobalProps(environment, prefix + TAG_GLOBAL);assert datasource != null;for(String db : datasource.split(",")){final String dbKey = prefix + db; //数据库列表Map<String,Object> datasourceProps = PropertyUtil.handle(environment,dbKey, Map.class);injectGlobals(datasourceProps, globalInfo);dataSourceMap.put(db,datasourceProps);}String defaultData = environment.getProperty(prefix + "default");defaultDataSourceConfig = PropertyUtil.handle(environment,prefix + defaultData, Map.class);injectGlobals(defaultDataSourceConfig, globalInfo);}public Map getGlobalProps(Environment env, String key){try {return PropertyUtil.handle(env,key, Map.class);} catch (Exception e) {return Collections.EMPTY_MAP;}}private void injectGlobals(Map<String,Object> origin,Map<String,Object> global){global.forEach((k,v)->{if(!origin.containsKey(k)){origin.put(k,v);}else{injectGlobals((Map<String, Object>) origin.get(k), (Map<String, Object>) global.get(k));}});}

DynamicDataSource 这个类继承了AbstractRoutingDataSource,通过获取ThreadLocal中的数据库名,动态切换数据源。

public class DynamicDataSource extends AbstractRoutingDataSource {@Value("wr-db-router.spring.datasource.default")private String defaultDatasource;@Overrideprotected Object determineCurrentLookupKey() {if(null == DBContextHolder.getDBKey()){return defaultDatasource;}else{return DBContextHolder.getDBKey();}}
}

我们通过重写determineCurrentLookupKey方法并设置对应的数据库名称,我们就可以实现切换数据源的功能了。

AbstractRoutingDataSource 主要源码如下:

 public Connection getConnection() throws SQLException {return this.determineTargetDataSource().getConnection();}...protected DataSource determineTargetDataSource() {Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");Object lookupKey = this.determineCurrentLookupKey();DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);if (dataSource == null && (this.lenientFallback || lookupKey == null)) {dataSource = this.resolvedDefaultDataSource;}if (dataSource == null) {throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");} else {return dataSource;}}

自定义MyBatisPlus的SpringBoot自动配置

MybatisPlus是默认使用的Mybatis的自带的DataSourceAutoConfiguration,但是我们已经将这个自定义了,所以我们也要去自定义一个MyBatisPlusAutoConfig,如果不自定义的话,系统启动将报错。代码如下:

@Configuration(proxyBeanMethods = false
)
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisPlusProperties.class})
@AutoConfigureAfter({DataSourceAutoConfig.class, MybatisPlusLanguageDriverAutoConfiguration.class})
public class MyBatisPlusAutoConfig  implements InitializingBean {
xxx
}

这个代码是直接拷贝了MyBatisPlusAutoConfiguration,只是将@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class})改为了
@AutoConfigureAfter({DataSourceAutoConfig.class, MybatisPlusLanguageDriverAutoConfiguration.class})

这样启动就不会报错了。

其他步骤

上面这些开发完,就差不多可以实现数据库的动态切换从而实现读写分离了,不过其中有一个方法PropertyUtil,这是自定义的一个可以读取properties某个前缀下的所有属性的一个工具类。代码如下:


import org.springframework.core.env.Environment;
import org.springframework.core.env.PropertyResolver;import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;public class PropertyUtil {private static int springBootVersion = 2;public static <T> T handle(final Environment environment,final String prefix,final Class<T> clazz){switch (springBootVersion){case 1:return (T) v1(environment,prefix);case 2:return (T) v2(environment,prefix,clazz);default:throw new RuntimeException("Unsupported Spring Boot version");}}public static Object v1(final Environment environment,final String prefix){try {Class<?> resolverClass = Class.forName("org.springframework.boot.bind.RelaxedPropertyResolver");Constructor<?> resolverConstructor = resolverClass.getDeclaredConstructor(PropertyResolver.class);Method getSubPropertiesMethod = resolverClass.getDeclaredMethod("getSubProperties", String.class);Object resolverObject = resolverConstructor.newInstance(environment);String prefixParam = prefix.endsWith(".") ? prefix : prefix + ".";return getSubPropertiesMethod.invoke(resolverObject, prefixParam);} catch (final ClassNotFoundException | NoSuchMethodException | SecurityException | InstantiationException| IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {throw new RuntimeException(ex.getMessage(), ex);}}private static Object v2(final Environment environment, final String prefix, final Class<?> targetClass) {try {Class<?> binderClass = Class.forName("org.springframework.boot.context.properties.bind.Binder");Method getMethod = binderClass.getDeclaredMethod("get", Environment.class);Method bindMethod = binderClass.getDeclaredMethod("bind", String.class, Class.class);Object binderObject = getMethod.invoke(null, environment);String prefixParam = prefix.endsWith(".") ? prefix.substring(0, prefix.length() - 1) : prefix;Object bindResultObject = bindMethod.invoke(binderObject, prefixParam, targetClass);Method resultGetMethod = bindResultObject.getClass().getDeclaredMethod("get");return resultGetMethod.invoke(bindResultObject);}catch (final ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException| IllegalArgumentException | InvocationTargetException ex) {throw new RuntimeException(ex.getMessage(), ex);}}
}

我将路由切换的功能逻辑单独拉成了一个SpringBootStarter,目录如下:
在这里插入图片描述
顺便介绍一下如何将以个项目在SpringBootStarter中自动装配
1.在resources中创建文件夹META-INF
2.创建spring.factories文件
3.在该文件中设置你需要自动装配的类

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.xxx.wrdbrouter.config.DataSourceAutoConfig,\com.xxx.wrdbrouter.config.MyBatisPlusAutoConfig

就先记录这些,目前正在进行主从库同步的相关内容。

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

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

相关文章

线上3D博物馆搭建简单吗?有何优势?有哪些应用场景?

随着科技的飞速发展&#xff0c;传统的博物馆参观方式正在经历一场前所未有的变革&#xff0c;在科技的“加持”下&#xff0c;不少博物馆凭借强大的技术、创意和美学实践&#xff0c;频频“出圈”&#xff0c;线上3D博物馆逐渐崛起&#xff0c;这不仅丰富了人们的文化体验&…

PyCharm 集成 Git

目录 1、配置 Git 忽略文件 2、定位Git 3、使用pycharm本地提交 3.1、初始化本地库 3.2、添加到暂存区 3.3、提交到本地库 3.4、切换版本 4、分支操作 4.1、创建分支 4.2、切换分支 4.3、合并分支 5、解决冲突 1、配置 Git 忽略文件 作用&#xff1a;与项目的实际…

【竞技宝】英超:曼城击败热刺,赢西汉姆联就夺冠

曼城在英超补赛中跟热刺相遇,这场比赛对于双方来说都必须赢。曼城要是拿不下热刺,联赛夺冠形势就不容乐观。热刺则是需要击败曼城,保留拿到下赛季欧冠的一线希望。所以,热刺和曼城开场就全力以赴。上半场热刺和曼城门将都做出精彩扑救,比分维持在0比0。下半场曼城金靴哈兰德发威…

关于电源1

电源的定义 广义定义&#xff1a;电源是将其它形式的能转换成电能的装置。 例如&#xff1a;发电机&#xff1a;将热能、水能、风能、核能、光照、震动等转化为电能的装置。 电池&#xff1a;将化学能转换为电能。 狭义定义&#xf…

Bootstrap Studio for Mac:打造专业级网页设计软件

对于追求高效与品质的设计师和开发者来说&#xff0c;Bootstrap Studio for Mac无疑是最佳选择。它建立在广受欢迎的Bootstrap框架之上&#xff0c;输出干净、语义化的HTML代码。同时&#xff0c;强大的CSS和SASS编辑器&#xff0c;支持自动建议和规则验证&#xff0c;让您的设…

交换机组网最常见的8大故障及解决方式

有朋友多次提到网络故障&#xff0c;其中在交换机组网时常见的故障比较多&#xff0c;为了便于大家排除这些故障&#xff0c;在此介绍一些常见的典型故障案例及处理思路。 故障1&#xff1a;交换机刚加电时网络无法通信 【故障现象】 交换机刚刚开启的时候无法连接至其他网络…

数据结构_链表基本操作的实现_代码_例题

一、基本操作实现 1.按位序插入&#xff08;带头节点&#xff09; 2.按位序插入&#xff08;不带头节点&#xff09; 3.指定结点的后插操作 4.指定结点的前插操作 5.按位序删除&#xff08;带头节点&#xff09; 6.指定结点的删除 7.按位查找&#xff0c;返回第i个元素&…

小朋友台灯什么品牌好,分享最好的台灯品牌排行榜

小朋友台灯什么品牌好&#xff1f;台灯作为我们日常生活中重要的桌面照明工具&#xff0c;对于办公族的工作和学生的学习都扮演着关键角色。长期使用质量不佳的台灯可能会对我们的视力健康造成不利影响&#xff0c;尤其是对于眼睛尚在发育阶段的青少年来说&#xff0c;这种影响…

Observability:介绍 OpenTelemetry Java 代理的 Elastic 发行版

作者&#xff1a;来自 Elastic Alexander Wert, Jack Shirazi, Jonas Kunz, Sylvain Juge 随着 Elastic 继续致力于 OpenTelemetry (OTel)&#xff0c;我们很高兴地宣布推出 OTel Java Agent 的 Elastic 发行版。 在这篇博文中&#xff0c;我们将探讨我们独特的发行版背后的基本…

P9748 [CSP-J 2023] 小苹果:做题笔记

目录 P9748 [CSP-J 2023] 小苹果 思路 代码 P9748 [CSP-J 2023] 小苹果 P9748 [CSP-J 2023] 小苹果 思路 先写几个看看规律 题意我们能看出来是三个三个一组的&#xff0c;然后每次取走的都是三个里面的第一个。我们应该很容易想到如果一轮的总数是三的倍数的话&#xff0…

一文解析嵌入式多核异构方案,东胜物联RK3588多核异构核心板系列一览

嵌入式人工智能快速发展&#xff0c;对于高性能计算需求越来越大。为了解决性能与功耗的平衡、通过并行化加速计算等&#xff0c;越来越多地嵌入式处理器使用同构多核、异构多核和协处理器的设计。 同时面对日益复杂的外部环境&#xff0c;国产嵌入式智能系统更离不开兼顾强实…

每天认识新职业——程序员

一、程序员是什么 程序员是从事程序开发、程序维护的基层工作人员。一般将程序员分为程序设计人员和程序编码人员&#xff0c;但两者的界限并不非常清楚。随着互联网的不断普及&#xff0c;网络上把男程序员称作“程序猿"&#xff0c;女程序员称作“程序媛"。或统称…

CSRF 攻击实验:Token 不存在绕过验证

前言 CSRF&#xff08;Cross-Site Request Forgery&#xff09;&#xff0c;也称为XSRF&#xff0c;是一种安全漏洞&#xff0c;攻击者通过欺骗用户在受信任网站上执行非自愿的操作&#xff0c;以实现未经授权的请求。 CSRF攻击利用了网站对用户提交的请求缺乏充分验证和防范…

navicat 无法连接mysql8

select host,user,authentication_string,plugin from mysql.user; alter user root%IDENTIFIED WITH mysql_native_password BY root; flush PRIVILEGES; select host,user,authentication_string,plugin from mysql.user;

运维别卷系列 - 云原生监控平台 之 04.prometheus 查询语句 promql 实践

文章目录 [toc]PromQL 简介什么是时间序列 PromQL 数据类型即时向量 Instant vector范围向量 Range vectorTime DurationsOffset modifier modifier 浮点值 Scalar字符串 String PromQL FUNCTIONSfloor()irate()rate()round()sort()sort_desc() PromQL 运算符算术运算符比较运算…

python 批量webp格式转换成jpg

首先&#xff0c;你需要安装Pillow库。如果还未安装&#xff0c;可以通过pip安装&#xff1a; pip install Pillow 创建一个Python脚本来读取webp文件&#xff0c;并将其转换为jpg格式。 只需修改source_folder和dest_folder变量为你的实际文件夹路径即可使用这个脚本。 fro…

阻抗控制理解之逆动态控制律

具有六个自由度的二阶机械系统&#xff0c;其特征是给定的质量、阻尼和刚度&#xff0c;称为机械阻抗。 用于运动控制的加速度解决方法&#xff0c;它旨在通过逆动力学控制律在加速度水平上解耦和线性化非线性机器人动力学。在与环境存在交互作用的情况下&#xff0c;控制律 考…

美港通正规股票杠杆交易突破3900点,欧线集运再创历史新高

查查配5月13日,欧线集运主连高开高走,盘中一度涨超13%,截至早盘收盘涨11.93%,突破3900点。4月以来,欧线集运主连累计涨超110%。 美港通证券以其专业的服务和较低的管理费用在市场中受到不少关注。该平台提供了实盘交易、止盈止损、仓位控制等功能,旨在为投资者提供更为全面的投…

短剧私域-快速引流变现

短剧的爆火&#xff0c;衍生出了很多周边项目。 比如免费看剧App&#xff0c;短剧搜索机器人&#xff0c;短剧付费圈子等等。 这些项目的本质&#xff0c;就是借助短剧的热度&#xff0c;把流量引到自己的鱼塘进行变现。 短剧机器人大家都知道&#xff0c;目前最火的一种玩法…

水电站机组油压自动化控制系统概述及优势介绍

一、系统背景 我国河流、湖泊分布广泛&#xff0c;落差巨大&#xff0c;蕴藏着丰富的资源优势&#xff0c;我国作为世界第二大能源消耗国&#xff0c;对于电力的需求是巨大的&#xff0c;水力发电具有高效、清洁、能量供给稳定充足的特点&#xff0c;因此&#xff0c;水电工程…