目录

  • 1、业务场景
  • 2、主要思路
  • 3、加载默认数据源
  • 4、多数据源规则配置
  • 5、程序启动加载子公司数据源
  • 6、自定义事务注解
  • 7、程序中调用

1、业务场景

因为业务业务需求,需要把基础数据与子公司业务数据分开,做分库处理,基础数据存储在主库中,业务数据根据子公司存储在不同的数据库中,程序业务逻辑需要同时操作两个或多个数据库,并且数据保持一致,因此引入多数据源事务处理

2、主要思路

1、项目启动默认加载主数据库,主数据库中存储子公司数据库相关信息
2、监听项目的启动,当项目启动时,读取主库中的子公司数据库链接信息,将数据库链接读入系统中
3、继承AbstractRoutingDataSource类,重写determineCurrentLookupKey方法的返回结果以达到在业务中动态切换数据库

3、加载默认数据源

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.dcx.datasource.DynamicDataSource;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.servlet.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/*** Created by xiang on 2021-08-25*/
@Configuration
public class DataSourceConfig {@Value("${spring.datasource.default.jdbc-url}")private String defaultDBUrl;@Value("${spring.datasource.default.username}")private String defaultDBUser;@Value("${spring.datasource.default.password}")private String defaultDBPassword;@Value("${spring.datasource.default.driver-class-name}")private String defaultDBDreiverName;@Value("${spring.datasource.initialSize}")private int initialSize;@Value("${spring.datasource.minIdle}")private int minIdle;@Value("${spring.datasource.maxActive}")private int maxActive;@Value("${spring.datasource.maxWait}")private int maxWait;@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")private int timeBetweenEvictionRunsMillis;@Value("${spring.datasource.minEvictableIdleTimeMillis}")private int minEvictableIdleTimeMillis;@Value("${spring.datasource.maxEvictableIdleTimeMillis}")private int maxEvictableIdleTimeMillis;@Value("${spring.datasource.validationQuery}")private String validationQuery;@Value("${spring.datasource.testWhileIdle}")private boolean testWhileIdle;@Value("${spring.datasource.testOnBorrow}")private boolean testOnBorrow;@Value("${spring.datasource.testOnReturn}")private boolean testOnReturn;@Beanpublic DynamicDataSource dynamicDataSource() {DynamicDataSource dynamicDataSource = DynamicDataSource.getInstance();DruidDataSource defaultDataSource = new DruidDataSource();defaultDataSource.setUrl(defaultDBUrl);defaultDataSource.setUsername(defaultDBUser);defaultDataSource.setPassword(defaultDBPassword);defaultDataSource.setDriverClassName(defaultDBDreiverName);dataSource( defaultDataSource);Map<Object, Object> map = new HashMap<>();map.put("default", defaultDataSource);dynamicDataSource.setTargetDataSources(map);dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);return dynamicDataSource;}public DruidDataSource dataSource(DruidDataSource datasource){/** 配置初始化大小、最小、最大 */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;}}

4、多数据源规则配置

/*** Created by xiang on 2021-08-25*/
public class DataSourceContextHolder {/*** 存储的是数据源的key*/private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();/*** 设置当前数据源key** @param*/public static synchronized void setDBType(String dbType) {contextHolder.set(dbType);}/*** 获取当前的数据源key** @return data source name*/public static String getDBType() {return contextHolder.get();}/*** 清除数据源*/public static void clearDBType() {contextHolder.remove();}
}
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;/*** Created by xiang on 2021-08-25*/
public class DynamicDataSource extends AbstractRoutingDataSource {private static DynamicDataSource instance;private static byte[] lock = new byte[0];/*** 抽象类AbstractRoutingDataSource中的resolvedDataSources存储了最终解析后的数据源* 由于没有提供get的方法,所以此处自己维护一个Map保存数据源,如果后续业务需要获取数据源可以从此处获取*/private static Map<Object, Object> dataSourceMap = new HashMap<Object, Object>();/*** 添加数据源*/@Overridepublic void setTargetDataSources(Map<Object, Object> targetDataSources) {super.setTargetDataSources(targetDataSources);dataSourceMap.putAll(targetDataSources);super.afterPropertiesSet();// 必须添加该句,否则新添加数据源无法识别到}/*** 获取数据源Map*/public Map<Object, Object> getDataSourceMap() {return dataSourceMap;}public static synchronized DynamicDataSource getInstance() {if (instance == null) {synchronized (lock) {if (instance == null) {instance = new DynamicDataSource();}}}return instance;}// 必须实现其方法protected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDBType();}//----------------------以下为事务下切换数据源相关配置--------------------------------/*** 保存当前线程使用了事务的数据库连接(connection)* 当我们自己管理事务的时候即可从此处获取到当前线程使用了哪些连接从而让这些被使用的连接commit/rollback/close*/private ThreadLocal<Map<String, ConnectWarp>> connectionThreadLocal = new ThreadLocal<>();public DataSource getDataSource(String key) {return (DataSource) dataSourceMap.get(key);}/*** mybatis在使用mapper接口执行sql的时候会从该方法获取connection执行sql* 如果事务是spring或者mybatis在管理,那么直接返回原生的connection* 如果是我们自己控制事务,则返回我们自己实现的ConnetWarp*++* @return Connection* @throws SQLException SQLException*/@Overridepublic Connection getConnection() throws SQLException {Map<String, ConnectWarp> stringConnectionMap = connectionThreadLocal.get();if (stringConnectionMap == null) {// 没开事物 直接返回return determineTargetDataSource().getConnection();} else {// 开了事物 从当前线程中拿 而且拿到的是 包装过的connect 只有手动去提交和关闭连接String currentName = (String) determineCurrentLookupKey();return stringConnectionMap.get(currentName);}}/*** 开启事物的时候,把连接放入 线程中,后续crud 都会拿对应的连接操作** @param key        子公司code* @param connection 连接*/public void bindConnection(String key, Connection connection) {Map<String, ConnectWarp> connectionMap = connectionThreadLocal.get();if (connectionMap == null) {connectionMap = new HashMap<>();connectionThreadLocal.set(connectionMap);}ConnectWarp connectWarp = new ConnectWarp(connection);connectionMap.put(key, connectWarp);}/*** 提交事物** @throws SQLException SQLException*/public void doCommit() throws SQLException {Map<String, ConnectWarp> stringConnectionMap = connectionThreadLocal.get();if (stringConnectionMap == null) {return;}for (String dataSourceName : stringConnectionMap.keySet()) {ConnectWarp connection = stringConnectionMap.get(dataSourceName);connection.realCommit();connection.realClose();}removeConnectionThreadLocal();}/*** 回滚事物** @throws SQLException SQLException*/public void rollback() throws SQLException {Map<String, ConnectWarp> stringConnectionMap = connectionThreadLocal.get();if (stringConnectionMap == null) {return;}for (String dataSourceName : stringConnectionMap.keySet()) {ConnectWarp connection = stringConnectionMap.get(dataSourceName);connection.rollback();connection.realClose();}removeConnectionThreadLocal();}protected void removeConnectionThreadLocal() {connectionThreadLocal.remove();}
}

5、程序启动加载子公司数据源

import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dcx.designer.connection.entity.DcxConnection;
import com.dcx.designer.connection.service.DcxConnectionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;
import org.springframework.web.context.ServletContextAware;import javax.annotation.Resource;
import javax.servlet.ServletContext;
import java.util.List;
import java.util.Map;/*** Greated by xiang on 2021/8/26* 加载子公司数据源*/
@Slf4j
@Service
public class InitDataListener implements InitializingBean, ServletContextAware {@Resourceprivate DcxConnectionService dcxConnectionService;@Overridepublic void afterPropertiesSet() throws Exception {}/*** 功能描述: <br>* 〈加载全部数据库链接〉** @Param: * @param servletContext* @Return: void* @Author: 85122* @Date: 2021/8/26 19:28*/@Overridepublic void setServletContext(ServletContext servletContext) {try {// 获取默认数据库链接Map<Object, Object> dataSourceMap = DynamicDataSource.getInstance().getDataSourceMap();DruidDataSource defaultDataSource = (DruidDataSource) dataSourceMap.get("default");// 添加筛选调节QueryWrapper<DcxConnection> wrapper = new QueryWrapper<>();wrapper.eq("isDeleted", 0);// 查询全部数据库链接List<DcxConnection> dcxConnectionList = dcxConnectionService.list(wrapper);for (int i = 0; i < dcxConnectionList.size(); i++) {DruidDataSource druidDataSource = new DruidDataSource();druidDataSource.setUrl(dcxConnectionList.get(i).getJdbcUrl());druidDataSource.setUsername(dcxConnectionList.get(i).getUserName());druidDataSource.setPassword(dcxConnectionList.get(i).getPassWord());druidDataSource.setDriverClassName(dcxConnectionList.get(i).getDriverClassName());dataSource(druidDataSource,defaultDataSource);dataSourceMap.put(dcxConnectionList.get(i).getDbType(), druidDataSource);}DynamicDataSource.getInstance().setTargetDataSources(dataSourceMap);} catch (Exception e) {e.printStackTrace();}}public DruidDataSource dataSource(DruidDataSource datasource,DruidDataSource defaultDataSource){/** 配置初始化大小、最小、最大 */datasource.setInitialSize(defaultDataSource.getInitialSize());datasource.setMaxActive(defaultDataSource.getMaxActive());datasource.setMinIdle(defaultDataSource.getMinIdle());/** 配置获取连接等待超时的时间 */datasource.setMaxWait(defaultDataSource.getMaxWait());/** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */datasource.setTimeBetweenEvictionRunsMillis(defaultDataSource.getTimeBetweenEvictionRunsMillis());/** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */datasource.setMinEvictableIdleTimeMillis(defaultDataSource.getMinEvictableIdleTimeMillis());datasource.setMaxEvictableIdleTimeMillis(defaultDataSource.getMaxEvictableIdleTimeMillis());/*** 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。*/datasource.setValidationQuery(defaultDataSource.getValidationQuery());/** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */datasource.setTestWhileIdle(defaultDataSource.isTestWhileIdle());/** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */datasource.setTestOnBorrow(defaultDataSource.isTestOnBorrow());/** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */datasource.setTestOnReturn(defaultDataSource.isTestOnReturn());return datasource;}
}

6、自定义事务注解

import java.lang.annotation.*;/*** 自定义注解** Greated by xiang on 2021/8/26*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface MyTransactional {}
import com.dcx.dbtable.service.ConnectString;
import com.dcx.utils.ComHelperBLL;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;import javax.sql.DataSource;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;/*** 定义了AOP切面拦截我们方法上是否有加上我们定义的@MyTransactional注解*/
@Component
@Slf4j
@Aspect
public class ExtTransactionalAop {/*** 环绕通知** @param joinPoint*/@Around(value = "@annotation(com.dcx.datasource.MyTransactional)")public Object around(ProceedingJoinPoint joinPoint) throws Exception {DynamicDataSource multiRouteDataSource = DynamicDataSource.getInstance();Map<Object, Object> dataSourceMap = multiRouteDataSource.getDataSourceMap();// 根据token 获取需要开启事务数据库 此处根据自己系统编写公用方法String dbType = ComHelperBLL.getDbType();List<String> dbTypeList = new ArrayList<>();if (StringUtils.isNotEmpty(dbType)) {dbTypeList.add("default");dbTypeList.add(dbType);} else {for (Object key : dataSourceMap.keySet()) {dbTypeList.add(String.valueOf(key));}}for (int i = 0; i < dbTypeList.size(); i++) {DataSource dataSource = (DataSource) dataSourceMap.get(dbTypeList.get(i));if (dataSource == null) {continue;}Connection connection = dataSource.getConnection();if (connection != null) {// 设置事务隔离级别connection.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);if (connection.getAutoCommit()) {connection.setAutoCommit(false);}}// 将连接绑定到当前线程multiRouteDataSource.bindConnection(String.valueOf(dbTypeList.get(i)), connection);}try {ConnectString.setCurDataSource00(0);Object result = joinPoint.proceed();//目标方法multiRouteDataSource.doCommit();return result;} catch (Exception e) {multiRouteDataSource.rollback();throw e;} catch (Throwable throwable) {throwable.printStackTrace();multiRouteDataSource.rollback();} finally {}return "系统错误";}

7、程序中调用

1、调用注解
2、切换数据源
DataSourceContextHolder.setDBType(“default”);

springboot多数据源动态切换,事务下切换数据源(非分布式事务)相关推荐

  1. 分布式事务与Seate框架:分布式事务理论

    推荐阅读: 这套Github上40K+star学习笔记,可以帮你搞定95%以上的Java面试 毫不夸张的说,这份SpringBoot学习指南能解决你遇到的98%的问题 给跪了!这套万人期待的 SQL ...

  2. 《深入理解分布式事务》第八章 TCC 分布式事务原理

    <深入理解分布式事务>第八章 TCC 分布式事务原理 文章目录 <深入理解分布式事务>第八章 TCC 分布式事务原理 一.TCC 核心思想 二.TCC 实现原理 1.TCC 核 ...

  3. 《深入理解分布式事务》第四章 分布式事务的基本概念和理论知识

    <深入理解分布式事务>第四章 分布式事务的基本概念和理论知识 文章目录 <深入理解分布式事务>第四章 分布式事务的基本概念和理论知识 一.分布式系统架构 1.简介 2.分布式事 ...

  4. 【分布式事务系列九】聊聊分布式事务

    为什么80%的码农都做不了架构师?>>>    #0 系列目录# 分布式事务 [分布式事务系列一]提出疑问和研究过程 [分布式事务系列二]Spring事务管理器PlatformTra ...

  5. 蚂蚁金服大规模分布式事务实践及四种分布式事务模式

    蚂蚁金服大规模分布式事务实践及四种分布式事务模式 作者: 绍辉 | 2019-07-30 13:55   收藏: 1 本文整理自蚂蚁金服技术专家.分布式事务 Seata 发起者之一张森(花名:绍辉)在 ...

  6. spring整合atomikos实现分布式事务的方法示例_分布式事务中的XA和JTA

    在介绍这两个概念之前,我们先看看是什么是X/Open DTP模型. X/Open X/Open,即现在的open group,是一个独立的组织,主要负责制定各种行业技术标准.X/Open组织主要由各大 ...

  7. 分布式事务模型--基于消息的分布式事务

    本文来说下分布式事务模型之基于消息的分布式事务 文章目录 概述 基于消息的分布式事务 基于事务消息的分布式事务 基于本地消息的分布式事务 特点剖析 本文小结 概述 事务是一组不可分组的操作集合,这些操 ...

  8. 分布式事务(三):分布式事务解决方案之TCC(Try、Confirm、Cancel)

    什么是TCC TCC是Try.Contirm.Cancel三个词语的缩写,TCC要求每个 分支事务实现三个操作:预处理Try.确认Contirm.撤销Cancel.Try操作业务检查以及资源预留,Co ...

  9. SpringBoot之JPA框架下如何使用JTA——分布式事务解决方案

    以前,笔者写过一篇博客,支付宝DTS方案,当然,只是仅仅是简单讨论了下分布式事务的解决方案.PS:笔者看了下相关评论,发现由于太简单,被不少人Diss了一通. 最近,笔者在自己的工程上,试图一次性解决 ...

最新文章

  1. python 多线程编程之_thread模块
  2. 2021年SDN和NFV的支出将超1580亿美元!
  3. u-boot命令寻找分析--find_cmd函数
  4. 【翻译】【CGWORLD】怪物猎人携带版3rd制作介绍
  5. 婚宴座位图html5,图解现代婚宴座位安排
  6. Spring与网关的集成
  7. 论文学习2-Incorporating Graph Attention Mechanism into Knowledge Graph Reasoning Based on Deep Reinforce
  8. android 插入耳机 使用自身mic录音_这样选用麦克风,耳机降噪效果会更好
  9. python面试题之python多线程与多进程的区别
  10. Mysql中Drop,Truncate,Delete的区别
  11. iOS CoreData (二) 版本升级和数据库迁移
  12. 项目总结 -谷粒学院
  13. 使用canvas把照片旋转任意角度
  14. c语言程序设计二级考试哪些题型,计算机二级考试题型
  15. 如何使用EDI系统解决对接多工厂的问题?
  16. 【生物信息】影像组学入门实践成长营(14天)
  17. 解决“此实现不是 Windows 平台 FIPS 验证的加密算法的一部分”
  18. MongoDB的安装与可视化工具Studio 3T的安装
  19. js判断字符串处于键盘三连键
  20. Very Good!!! - React 入门实例教程

热门文章

  1. 多任务进化优化算法(二) 多因子进化算法(MFEA)的理论基础、多任务贝叶斯优化以及MFEAII简介
  2. 传输安全里从LDAP到LDAPS的安全加固
  3. 【机器学习】分类决策树与回归决策树案例
  4. 极其精简的PHP框架WJW
  5. 利用SNMP攻击思科等交换机路由器等设备
  6. 单片机双机通信c语言实验心得,双机通讯实验报告
  7. 出海游戏怎样触达全球Microsoft Store和Xbox平台的氪金玩家?
  8. 算法设计与分析 树形dp
  9. python对配置文件的读写
  10. SAAS系统架构之数据存储方案