Spring-Boot 多数据源配置+动态数据源切换+多数据源事物配置实现主从数据库存储分离
一、基础介绍
多数据源字面意思,比如说二个数据库,甚至不同类型的数据库。在用SpringBoot开发项目时,随着业务量的扩大,我们通常会进行数据库拆分或是引入其他数据库,从而我们需要配置多个数据源。
二、项目目录截图
三、多数据源SQL结构设计如下(简单的主从关系):
PS:创建两个库用于搭建项目中主从使用不同的数据库,表可以随意定义。
四、配置编码
1.数据源自定义注解,DataSource.java
/**
* 数据源自定义注解
*/ @Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
DataSourcesType name() default DataSourcesType.MASTER; }
2.数据源类型枚举类定义,DataSourcesType.java
/**
* 数据源类型
*/
public enum DataSourcesType {
/**
* 主库
*/
MASTER, /**
* 从库
*/
SLAVE }
3.多数据源application.yml配置文件配置
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
master:
url: jdbc:mysql://127.0.0.1:3306/master?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
slave:
enable: true
url: jdbc:mysql://127.0.0.1:3306/slave?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: 123456
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙,此处是filter修改的地方
filters:
commons-log.connection-logger-name: stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
# 配置 DruidStatFilter
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: .js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*
stat-view-servlet:
enabled: true
url-pattern: /druid/*
# IP 白名单,没有配置或者为空,则允许所有访问
allow: 127.0.0.1
# IP 黑名单,若白名单也存在,则优先使用
deny: 192.168.31.253
# 禁用 HTML 中 Reset All 按钮
reset-enable: false
# 登录用户名/密码
login-username: root
login-password: 123
# 慢SQL记录
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
4.数据源配置文件属性定义,DataSourceProperties.java
/**
* 数据源配置文件
*/
@Setter
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.druid")
public class DataSourceProperties { private int initialSize; private int minIdle; private int maxActive; private int maxWait; private int timeBetweenEvictionRunsMillis; private int minEvictableIdleTimeMillis; private int maxEvictableIdleTimeMillis; private String validationQuery; private boolean testWhileIdle; private boolean testOnBorrow; private boolean testOnReturn; public DruidDataSource setDataSource(DruidDataSource datasource) { datasource.setInitialSize(initialSize);
/** 配置初始化大小、最小、最大 */
datasource.setInitialSize(initialSize);
datasource.setMaxActive(maxActive);
datasource.setMinIdle(minIdle);
/** 配置获取连接等待超时的时间 */
datasource.setMaxWait(maxWait);
/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
/** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);
/**
* 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
*/
datasource.setValidationQuery(validationQuery);
/** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
datasource.setTestWhileIdle(testWhileIdle);
/** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnBorrow(testOnBorrow);
/** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
datasource.setTestOnReturn(testOnReturn);
return datasource;
}
5.多数据源切换处理,DynamicDataSourceContextHolder.java
/**
* 数据源切换处理
*/
public class DynamicDataSourceContextHolder { public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); /**
*此类提供线程局部变量。这些变量不同于它们的正常对应关系是每个线程访问一个线程(通过get、set方法),有自己的独立初始化变量的副本。
*/
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); /**
* 设置当前线程的数据源变量
*/
public static void setDataSourceType(String dataSourceType) {
log.info("已切换到{}数据源", dataSourceType);
contextHolder.set(dataSourceType);
} /**
* 获取当前线程的数据源变量
*/
public static String getDataSourceType() {
return contextHolder.get();
} /**
* 删除与当前线程绑定的数据源变量
*/
public static void removeDataSourceType() {
contextHolder.remove();
} }
6.获取数据源(依赖于 spring) 定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换,DynamicDataSource.java
/**
* 获取数据源(依赖于 spring) 定义一个类继承AbstractRoutingDataSource实现determineCurrentLookupKey方法,该方法可以实现数据库的动态切换
*/
public class DynamicDataSource extends AbstractRoutingDataSource { public static DynamicDataSource build() {
return new DynamicDataSource();
} /**
* 获取与数据源相关的key
* 此key是Map<String,DataSource> resolvedDataSources 中与数据源绑定的key值
* 在通过determineTargetDataSource获取目标数据源时使用
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
} }
7.数据源核心配置类,DataSourceConfiguration.java
/**
* 数据源配置类
*/
@Configuration
public class DataSourceConfiguration { /**
* 主库
*/
@Bean
@ConfigurationProperties("spring.datasource.druid.master")
public DataSource masterDataSource(DataSourceProperties dataSourceProperties) {
return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build());
} /**
* 从库
*/
@Bean
@ConditionalOnProperty( prefix = "spring.datasource.druid.slave", name = "enable", havingValue = "true")//是否开启数据源开关---若不开启 默认适用默认数据源
@ConfigurationProperties("spring.datasource.druid.slave")
public DataSource slaveDataSource(DataSourceProperties dataSourceProperties) {
return dataSourceProperties.setDataSource(DruidDataSourceBuilder.create().build());
} /**
* 设置数据源
*/
@Bean(name = "dynamicDataSource")
@Primary
public DynamicDataSource dynamicDataSource(DataSource masterDataSource, DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
DynamicDataSource dynamicDataSource = DynamicDataSource.build();
targetDataSources.put(DataSourcesType.MASTER.name(), masterDataSource);
targetDataSources.put(DataSourcesType.SLAVE.name(), slaveDataSource);
//默认数据源配置 DefaultTargetDataSource
dynamicDataSource.setDefaultTargetDataSource(masterDataSource);
//额外数据源配置 TargetDataSources
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.afterPropertiesSet();
return dynamicDataSource;
} }
8.多数据源切面配置类,用于获取注解上的注解,进行动态切换数据源,DynamicDataSourceAspect.java
@Aspect
@Component
@Order(-1) // 保证该AOP在@Transactional之前执行
public class DynamicDataSourceAspect { protected Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.fuzongle.tankboot.common.annotation.DataSource)"
+ "|| @within(com.fuzongle.tankboot.common.annotation.DataSource)")
public void dsPointCut() {
} @Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Method targetMethod = this.getTargetMethod(point);
DataSource dataSource = targetMethod.getAnnotation(DataSource.class);//获取要切换的数据源
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.name().name());
}
try {
return point.proceed();
}
finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.removeDataSourceType();
}
} /**
* 获取目标方法
*/
private Method getTargetMethod(ProceedingJoinPoint pjp) throws NoSuchMethodException {
Signature signature = pjp.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method agentMethod = methodSignature.getMethod();
return pjp.getTarget().getClass().getMethod(agentMethod.getName(), agentMethod.getParameterTypes());
}
}
9.编写业务逻辑,切换从库查询数据。
10.编写测试方法,调用查询业务,查看是否切换数据源是否生效。
PS:这种多数据源的动态切换确实可以解决数据的主从分库操作,但是却有一个致命的BUG,那就是事务不但失效而且无法实现
一致性,因为涉及到跨库,因此我们必须另想办法来实现事务的ACID原则
以上配置所有源码地址:https://gitee.com/fuzongle/java-bucket)
注意:
1.如果有任何不懂的地方可以关注公众号就可以加我微信,随时欢迎互相帮助。
2.技术交流群QQ:422167709。
3.如果希望学习更多,希望微信扫码,长按扫码,帮忙关注一下,举手之劳,当您无助的时候真的能帮你。非常感谢您关注公众号 "编程小乐"。
Spring-Boot 多数据源配置+动态数据源切换+多数据源事物配置实现主从数据库存储分离的更多相关文章
- Spring Boot 揭秘与实战 自己实现一个简单的自动配置模块
文章目录 1. 实战的开端 – Maven搭建 2. 参数的配置 - 属性参数类 3. 真的很简单 - 简单的服务类 4. 自动配置的核心 - 自动配置类 5. spring.factories 不要 ...
- Spring Boot干货系列:(七)默认日志框架配置
Spring Boot干货系列:(七)默认日志框架配置 原创 2017-04-05 嘟嘟MD 嘟爷java超神学堂 前言 今天来介绍下Spring Boot如何配置日志logback,我刚学习的时候, ...
- Spring Boot 项目学习 (二) MySql + MyBatis 注解 + 分页控件 配置
0 引言 本文主要在Spring Boot 基础项目的基础上,添加 Mysql .MyBatis(注解方式)与 分页控件 的配置,用于协助完成数据库操作. 1 创建数据表 这个过程就暂时省略了. 2 ...
- Spring Boot教程(三十八)使用MyBatis注解配置详解(1)
之前在Spring Boot中整合MyBatis时,采用了注解的配置方式,相信很多人还是比较喜欢这种优雅的方式的,也收到不少读者朋友的反馈和问题,主要集中于针对各种场景下注解如何使用,下面就对几种常见 ...
- Spring Boot 2.x基础教程:使用MyBatis的XML配置方式
上一篇我们介绍了如何在Spring Boot中整合我们国人最常用的MyBatis来实现对关系型数据库的访问.但是上一篇中使用了注解方式来实现,而对于很多MyBatis老用户还是习惯于XML的开发方式, ...
- Spring Boot + Mybatis多数据源和动态数据源配置
文章转自 https://blog.csdn.net/neosmith/article/details/61202084 网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种 ...
- Spring Boot数据访问之动态数据源切换之使用注解式AOP优化
在Spring Boot数据访问之多数据源配置及数据源动态切换 - 池塘里洗澡的鸭子 - 博客园 (cnblogs.com)中详述了如何配置多数据源及多数据源之间的动态切换.但是需要读数据库的地方,就 ...
- Spring Boot:实现MyBatis动态数据源
综合概述 在很多具体应用场景中,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库.又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动态数据 ...
- Spring Boot系列:七、 实现Mybatis多数据源切换
一.引入相关maven配置 mybatis; mysql驱动:jdbc <dependency> <groupId>org.mybatis.spring.boot</g ...
随机推荐
- 微信小程序踩坑之前端问题处理篇
近期完成了一个小程序,自己做的前后端开发.真是惨哭我了o(╥﹏╥)o,下面几点希望大家可以避雷. 首先,想先介绍一下我遇到问题的解决思路: 1.先在postman调试接口,看数据获取是否正常, 2.在 ...
- Day8-微信小程序实战-交友小程序-首页用户列表渲染及多账号调试及其点赞功能的实现
在这之前已经把编辑个人的所有信息的功能已经完成了 之后先对首页的列表搞动态的,之前都是写死的静态 1.之前都是把好友写死的,现在就在js里面定义一个数组,用循环来动态的绑定 在onReady中定义,取 ...
- 3D 立体动态图 代码:
CSS样式: <style>*{ margin:0; padding:0;}body{ max-width: 100%; min-width: 100%; height: 100%; ba ...
- redis过期键的策略
一.过期时间设置: 127.0.0.1:6379> expire key seconds //设置键的过期时间为多少秒 127.0.0.1:6379> setex key seconds ...
- 关于html属性中onSubmit事件属性的使用
看到一段onSubmit事件属性的例子,如下: <!DOCTYPE html> <html> <head> <script> function vali ...
- .NET 5.0预览版6发布:支持Windows ARM64设备
2020年6月25日,微软dotnet团队在博客宣布了第六个 .NET 5.0 的预览版:https://devblogs.microsoft.com/dotnet/announcing-net-5- ...
- vue+iview多条联动,for循环data是函数
问题:多条for循环出的数据二级联动for循环出多条数据,每条数据都有一个二级联动,每次下拉一级联动,二级的选项都是变化的. 思考刚开始一直想不出如何实现二级联动下拉的数据动态变化,因为之前一直都是v ...
- SpringBoot--防止重复提交(锁机制---本地锁、分布式锁)
防止重复提交,主要是使用锁的形式来处理,如果是单机部署,可以使用本地缓存锁(Guava)即可,如果是分布式部署,则需要使用分布式锁(可以使用zk分布式锁或者redis分布式锁),本文的分布式锁以red ...
- Electron: 如何以 Vue.js, Vuetify 开始应用
Electron: 使用 JavaScript, HTML 和 CSS 等 Web 技术创建原生程序的框架 Vue.js: Web 前端用于构建用户界面的渐进式框架 Vuetify: Vue.js 的 ...
- Idea+springboot入坑之路
环境准备 IDEA 社区版: 2019.3 jdk: 1.8.0_241 tomcat: 7.0.99 maven: 3.6.3 spring-boot:2.2.5.RELEASE 插件 spring ...