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

主从同步配置方法

由于我自己只有一个服务器,于是我以本地为从库,云服务器为主库,两台MySQL服务器配置了主从同步。

一、修改主库的配置文件,my.cnf

# log config
log-bin = mysql-bin     #开启mysql的binlog日志功能
sync_binlog = 1         #控制数据库的binlog刷到磁盘上去 , 0 不控制,性能最好,1每次事物提交都会刷到日志文件中,性能最差,最安全
binlog_format = mixed   #binlog日志格式,mysql默认采用statement,建议使用mixed
expire_logs_days = 7                           #binlog过期清理时间
max_binlog_size = 100m                    #binlog每个日志文件大小
binlog_cache_size = 4m                        #binlog缓存大小
max_binlog_cache_size= 512m              #最大binlog缓存大
binlog-ignore-db=mysql #不生成日志文件的数据库,多个忽略数据库可以用逗号拼接,或者 复制这句话,写多行auto-increment-offset = 1     # 自增值的偏移量
auto-increment-increment = 1  # 自增值的自增量
slave-skip-errors = all #跳过从库错误

2、修改后需要重启MySQL服务
3、建复制用户

CREATE USER repl_user IDENTIFIED BY 'repl_root';
CREATE USER 'repl_user'@'192.168.0.136' IDENTIFIED BY 'repl_root';

给复制用户进行复制授权

grant replication slave on *.* to 'repl_user'@'%';
grant replication slave on *.* to 'repl_user'@'192.168.0.136';FLUSH PRIVILEGES;

修改复制用户对应的plugin

alter user 'repl_user'@'%' identified with mysql_native_password by 'repl_root';
alter user 'repl_user'@'192.168.0.136' identified with mysql_native_password by 'repl_root';

二、配置从库
1、修改从库配置文件

[mysqld]
server-id = 2
log-bin=mysql-bin
relay-log = mysql-relay-bin
##不同步的库表
replicate-wild-ignore-table=mysql.%
replicate-wild-ignore-table=test.%
replicate-wild-ignore-table=information_schema.%
replicate-wild-ignore-table=www_dwurl_site.%
replicate-wild-ignore-table=dragonwealths_co.%

2、进入从库MySQL,创建连接主库

change master to
master_host='xxxxx', ##主库IP地址
master_user='repl_user', ##复制用户
master_password='repl_root',##复制用户密码
master_port=3306,##主库port
master_log_file='mysql-bin.000009', ##主库最新的logbin文件
master_log_pos=21005389,##主库最新的logbin的position
master_retry_count=60,##重连次数
master_heartbeat_period=10000;##心跳

怎么获取master_log_file和master_log_pos?
在主库中输入show master status;
在这里插入图片描述
然后在从库中输入启动从库命令:start slave顺便说一下stop slave停止从库

接下来输入命令show slave status\G
在这里插入图片描述
只要Slave_IO_Running:Yes和Slave_SQL_Running:Yes,这样就表示已经主从同步成功了。

然后测试一下:
主库数据:
在这里插入图片描述

从库数据:
在这里插入图片描述
没有问题,但是昨天进行配置的时候Slave_SQL_Running是No,然后今天重新停止从库再重新配置一下change master to ,就没问题了。

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

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

相关文章

【无标题】海图微电子产品

一、HT2300 1、产品介绍 HT2300在全分辨率 (1920 H 1080 V) 下&#xff0c;它们的帧率可达2500fps。加上全局快门像素和低噪声等特性&#xff0c;可满足对高分辨率&#xff0c;高速CMOS图像传感器的需求&#xff0c;适用于科学研究&#xff0c;工业检测和数字影视中高速视频捕捉…

汇凯金业:贵金属投资如何操作

投资贵金属虽然可能看起来令人生畏&#xff0c;但只要你知道如何操作&#xff0c;就可能会变得实际可行。以下是操作贵金属投资的基本步骤&#xff1a; 1. 了解市场 第一步是学习贵金属投资的基础知识&#xff0c;了解市场的运作方式&#xff0c;类型的区别(如黄金、白银、铂…

JETBRAINS IDES 分享一个2099通用试用码,支持一键升级!DataGrip 2024 版

文章目录 废话不多说上教程&#xff1a;&#xff08;动画教程 图文教程&#xff09;一、动画教程激活 与 升级&#xff08;至最新版本&#xff09; 二、图文教程 &#xff08;推荐&#xff09;Stage 1.下载安装 toolbox-app&#xff08;全家桶管理工具&#xff09;Stage 2 : 下…

【手势识别-UILongPressGestureRecognizer长按 Objective-C语言】

一、我们来说这个长按啊, 1.长按这个手势,也是,步骤都是一样的,首先,也是这三大步啊, 1)创建手势对象 2)对某一个view添加手势 3)实现手势的方法 首先,也是三大步, 1)创建手势对象:首先,你要告诉我,你要使用哪一个手势,我要使用一个叫做UILongPressGesture…

【吊打面试官系列】Java高并发篇 - 创建线程的有哪些方式?

大家好&#xff0c;我是锋哥。今天分享关于 【创建线程的有哪些方式&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 创建线程的有哪些方式&#xff1f; 1、继承 Thread 类创建线程类 2、通过 Runnable 接口创建线程类 3、通过 Callable 和 Future 创建线程 …

【Maven】Nexus私服简介_下载安装_登录

1、简介 1.1介绍 Nexus私服&#xff0c;也被称为Maven仓库管理器&#xff0c;是许多公司在自己的局域网内搭建的远程仓库服务器。提供了强大的仓库管理功能和构件搜索功能&#xff0c;使得开发人员能够更方便地管理和使用Maven项目中的依赖库。 1.2作用 内网访问&#xff1…

JAVA面试库

1、基础 1.1、面向对象编程有哪些特性 1、抽象 抽象就是对同一个目标的共有的属性、特征、方法、功能、行为等进行抽取并归纳总结&#xff0c;它是一种将复杂现实简单化为模型的过程&#xff0c;它关注的是对象行为&#xff0c;而不用关注具体的实现细节。 在面向对象编程中…

CPT7数据保存详细步骤

一、连接设备、打开NovAtelConnect 软件 (1)点击1,并在2中输入如下命令: LOG RANGEB ONTIME 1 // 输出原始数据记录在板卡LOG RAWEPHEMB ONTIME 1 // 输出 GPS 原始星历记录在板卡LOG bdsephemerisb ONTIME 1 // 输出

关爱内向儿童:理解与支持助力成长

引言 每个孩子都是独特的&#xff0c;有些孩子天生性格外向&#xff0c;善于表达&#xff0c;而有些孩子则比较内向&#xff0c;喜欢独处。内向并不是缺点&#xff0c;而是一种性格特质。然而&#xff0c;内向的孩子在社交和学习过程中可能会面临一些挑战。本文将探讨内向儿童…

沉钒废水回收钒

沉钒废水处理与钒回收的重要性 沉钒废水是含钒元素的特殊废水&#xff0c;钒在工业生产中广泛应用&#xff0c;但其排放造成资源浪费与环境威胁。为实现钒的有效回收&#xff0c;研究和实践了多种处理技术。 沉钒废水处理技术 1. 化学沉淀法&#xff1a;添加沉淀剂&#xff…

【Linux】linux | 配置系统日志 | 安全日志 | 操作日志 | 登录日志

一、诉求 1、linux服务器开启日志功能&#xff0c;并记录10个月的登录 二、操作 1、进入目录 cd /etc 2、编辑配置 vi logrotate.conf 3、复制配置 /var/log/wtmp {monthlycreate 0664 root utmpminsize 1Mrotate 10 }/var/log/btmp {missingokmonthlycreate 0600 root …

【动态规划】子序列问题II|最长定差子序列|最长的斐波那契数列的长度|最长等差数列|等差数列的划分

一、最长定差子序列 1218. 最长定差子序列 算法原理&#xff1a; &#x1f4a1;细节&#xff1a; 1.正常创建dp表&#xff0c;分析状态转移方程&#xff1a;可能b存在于多个不同的位置&#xff0c;那么要用哪个下标的dp呢&#xff1f; 用最后一个b的&#xff0c;因为用前面的可…

C++ 将字符串解析为argc、argv

文章目录 前言一、如何实现&#xff1f;1、实现split2、split双引号3、奇数下标元素加入结果4、偶数下标元素split空格 二、完整代码三、使用示例1、解析命令行2、构造argc、argv 总结 前言 一般开启子进程的时候&#xff0c;需要传参数&#xff0c;通常直接传输命令行字符串&…

怎么做微信在线预约

在快节奏的现代生活中&#xff0c;我们总是追求更高效、更便捷的服务体验。而微信&#xff0c;这个拥有数亿用户的社交平台&#xff0c;早已不仅仅是一个聊天工具&#xff0c;它更是一个融合了多种功能的综合性服务平台。今天&#xff0c;就让我们一起探讨如何通过微信在线预约…

软考--试题六--中介者模式(Mediator)

中介者模式(Meditor) 意图 用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用&#xff0c;从而使其耦合松散&#xff0c;而且可以独立地改变它们之间的交互 结构 适用性 1、一组对象以定义良好但是复杂的方式进行通信&#xff0c;产生的相互依赖关…

机房、配电室可视化运维这么卷?不搞3D,出门没法打招呼。

机房和配电室的可视化运维确实可以非常复杂和卷。通过使用3D技术&#xff0c;可以更加直观地展示机房和配电室的布局、设备分布和运行状态。 以下是一些与机房和配电室可视化运维相关的关键点&#xff1a; 3D建模&#xff1a;使用计算机图形学和3D建模软件&#xff0c;可以创建…

亚马逊测评真人号与自养号:如何选择?区别与作用全面解析

亚马逊卖家都希望能打造出热销产品的产品列表&#xff0c;因为评论对于列表的曝光和流量有着巨大的影响。然而&#xff0c;获取有效的产品评论并不容易&#xff0c;许多卖家为了提高自己产品在同类别中的竞争力&#xff0c;选择进行测评。测评可以快速提高产品的排名、权重和销…

AGV小车有什么优点?后期将在各行业逐渐取代人工物料搬运

AGV 随着工厂自动化、计算机集成制造系统技术的逐步发展、及柔性制造系统、自动化立体仓库的广泛应用&#xff0c;AGV作为连接和调节离散型物流管理系统使作业连续化的必要自动化搬运装卸手段&#xff0c;其应用范围和技术水平有了更为迅猛的发展。 AGV立体仓库 随着AGV自动化技…

WebGL软件的开发框架

WebGL&#xff08;Web Graphics Library&#xff09;是一种用于在网页浏览器中实现3D图形渲染的JavaScript API。它允许开发者利用图形处理单元&#xff08;GPU&#xff09;来实时渲染复杂的3D场景&#xff0c;从而创建出令人惊叹的交互式体验。在WebGL开发中&#xff0c;有一些…

记PLSQL链接Oracle数据库

一、环境 Windows环境安装plsql工具 Oracle部署在服务器上面。 由于我之前在本地Windows安装了一个Oracle数据库&#xff0c;结果导致之前已经在连接的PLSQL链接不上。 二、操作 PLSQL工具正常安装&#xff0c;主要就是一些Oracle的一些配置&#xff0c;和oracle客户端。 o…