场景描述:

前一阵子接手的新项目中需要使用2个数据源。

一个叫行云数据库,一个叫OceanBase数据库。

就是说,我有时候查询要查行云的数据,有时候查询要查 OceanBase 的数据,咋办?

废话不多说, 下面以mysql为例,开整。

一、环境依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>

二、实现思路

在进行下一步之前,我们必须要知道 SpringBoot 自动配置的相关原理,因为之前,我们大多是单数据源。

# 配置mysql数据库
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: root

从前只是会用,会配置,甚至都是别人给配置的自己直接拿来用。

但是要搞懂动态数据源就必须要先搞懂自动配置

0.o?让我看看怎么个事儿之SpringBoot自动配置

现在我们要实现多数据源,并且可以自动切换

也就是我 A 查询连接的是行云数据库。

而我 B 查询却连接的是 OceanBase 数据库。

怎么办?

那第一个肯定就不能再使用DataSourceAutoConfigurtation了。

我直接反手一个 exclude 。

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})

然后呢?

Spring boot想得很周到,它提供了AbstractRoutingDataSource 抽象类。

这个类能根据用户定义的规则选择当前的数据源


有同学要问了:

AbstractRoutingDataSource 是什么东西?

AbstractRoutingDataSource 是一个抽象类。

它继承了 AbstractDataSource 抽象类。

而 AbstractDataSource 实现了 DataSource 接口。

也就是说:AbstractRoutingDataSource 他实际上就是一个DataSource 。

AbstractRoutingDataSource 中有两个关键方法。

setTargetDataSources(Map<Object, Object> targetDataSources)

第一个方法见名知意,设置目标数据源(复数也就是多个)。

protected abstract Object determineCurrentLookupKey();

第二个方法是仅有的一个抽象方法,需要开发者具体实现,也可以见名知意,决定当前使用(目标数据源中的)哪个

我们要做的是什么?

我们准备 2 个数据源,全部配置好放进 Map<Object, Object> targetDataSources 里备用。

我们继承 AbstractRoutingDataSource 并实现抽象方法 determineCurrentLookupKey() 。

当我们继承AbstractRoutingDataSource时我们自身也是一个数据源。

对于数据源必然有连接数据库的动作。

只是AbstractRoutingDataSource的getConnection()方法里实际是调用determineTargetDataSource()返回的数据源的getConnection()方法。

这样我们可以在执行查询之前,设置使用的数据源。

实现可动态路由的数据源,在每次数据库查询操作前执行。

它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。

我知道肯定有人看不懂要上嘴脸了:

Talk is cheap, show me the FUCKING code !!!

2.1 配置文件

spring:
datasource:
dynamic:
db1:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db1?serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: root
db2:
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2?serverTimezone=Asia/Shanghai&allowMultiQueries=true
username: root
password: root

2.2 自定义动态数据源

DynamicDataSource继承AbstractRoutingDataSource。

public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSource();
}
}

这里的determineCurrentLookupKey方法,需要返回一个数据源。

又有同学问了:

DynamicDataSourceContextHolder 又是什么东西?

public class DynamicDataSourceContextHolder {
public static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); public static String getDataSource() {
return CONTEXT_HOLDER.get();
} public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
} public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}

看到 Context 应该很熟悉了,跟程序上下文有关。

它的作用就是你查询数据库的时候用哪个数据源,就 setDataSource 哪个。

还有点懵?没事,继续往下看。

@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DynamicDataSourceConfig {
@Bean("db1")
@ConfigurationProperties(prefix = "spring.datasource.dynamic.db1")
public DataSource db1() {
return DataSourceBuilder.create().build();
}
@Bean("db2")
@ConfigurationProperties(prefix = "spring.datasource.dynamic.db2")
public DataSource db2() {
return DataSourceBuilder.create().build();
} @Bean
@Primary
public DataSource dataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("db1", db1());
dataSourceMap.put("db2", db2()); DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 需要设置的多数据源
dynamicDataSource.setTargetDataSources(dataSourceMap);
// 主数据源/默认数据源
dynamicDataSource.setDefaultTargetDataSource(db1());
return dynamicDataSource;
}
}

这是比较常见的自定义数据源配置了。

可以看到一共注册了3个数据源。

但是最后一个DynamicDataSource有 @Primary 注解,它表明这个数据源优先级更高。

DynamicDataSource中设置了dataSourceMap,也就是保存了 db1 和 db2。

以上我们动态数据源配置的工作就做完了。

我们以实际查询中的操作完整捋一遍这当中到底发生了什么!

@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
*使用db2数据源
*/
public void saveUser(UserDto userDto) {
DynamicDatasourceHolder.setDataSource("db2");
User user = new User();
user.setUName(userDto.getName());
userMapper.insert(user);
DynamicDatasourceHolder.removeDataSource("db2");
}
}

首先,DynamicDatasourceHolder 设置了数据源 db2 。

CONTEXT_HOLDER 中就保存了一个 “db2” 字符串。

userMapper 进行数据库操作之前,MyBatis 框架替我们做了一些事。

其中一件事是获取数据库连接

MyBatis 就在想:我得找个 DataSource ,因为DataSource 有getConnection() 方法。

谁是 DataSource ?

继承了 AbstractRoutingDataSource 的 DynamicDataSource 大声喊到:我是 !

开始连接数据库!

@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}

连接哪个?

protected DataSource determineTargetDataSource() {
// 哥,看这一行!
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
return dataSource;
}

连接这个!

@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSource();
}

连接完成!

insert 完成!

removeDataSource("db2") !

每次这样用嫌麻烦?

办他!

三、动态数据源注解@DS

看这部分之前需要一些前置知识点:

Java注解,看完就会用

师爷,翻译翻译什么叫AOP

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DS {
String value() default "db1";
}
@Component
@Aspect
public class DynamicDataSourceAspect { @Pointcut("@annotation(com.example.demo.annotation.DS)")
public void dynamicDataSourcePointCut() { } @Around("dynamicDataSourcePointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dataSourceKey = "db1";
// 类上的注解
Class<?> aClass = joinPoint.getTarget().getClass();
DS annotation = aClass.getAnnotation(DS.class); // 方法上的注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
DS annotationMethod = signature.getMethod().getAnnotation(DS.class); if (Objects.nonNull(annotationMethod)) {
dataSourceKey = annotationMethod.value();
} else {
dataSourceKey = annotation.value();
}
// 设置数据源
DynamicDataSourceContextHolder.setDataSource(dataSourceKey);
try {
return joinPoint.proceed();
}finally {
DynamicDataSourceContextHolder.clearDataSource();
}
}
}

具体我就不讲解了,自己对照前两篇文章研究一下吧。


最后再补充一下,动态数据源实际上有现成的依赖包可以用,可以参考使用,地址如下:

https://github.com/baomidou/dynamic-datasource


往期推荐:

师爷,翻译翻译什么叫AOP

0.o?让我看看怎么个事儿之SpringBoot自动配置

终于搞懂动态代理了!

学会@ConfigurationProperties月薪过三千

不是银趴~是@Import!

@Conditional+@Configuration有没有搞头?

SpringBoot实现动态数据源配置的更多相关文章

  1. Springboot+Druid 动态数据源配置监控

    一.引入maven依赖,使用 starter 与原生 druid 依赖配置有所不同 <dependency> <groupId>com.alibaba</groupId& ...

  2. 基于springboot的多数据源配置

    发布时间:2018-12-11   技术:springboot1.5.1 + maven3.0.1+ mybatis-plus-boot-starter2.3.1 + dynamic-datasour ...

  3. springboot+ibatis 多数据源配置

    这个是boot基本版本包,因为我用的打包方式是war所以去除掉了boot内置的tomcat,但是为了方便测试又引入了内置tomcat,只要添加<scope>provided</sco ...

  4. Druid动态数据源配置

    上文已经讲了单个数据源的Druid的配置(http://www.cnblogs.com/nbfujx/p/7686634.html) Druid动态数据源配置 主要是继承AbstractRouting ...

  5. Spring Boot2.x 动态数据源配置

    原文链接: Spring Boot2.x 动态数据源配置 基于 Spring Boot 2.x.Spring Data JPA.druid.mysql 的动态数据源配置Demo,适合用于数据库的读写分 ...

  6. springboot之多数据源配置JdbcTemplate

    springboot多数据源配置,代码如下 DataSourceConfig package com.rookie.bigdata.config; import org.springframework ...

  7. Spring Boot + Mybatis多数据源和动态数据源配置

    文章转自 https://blog.csdn.net/neosmith/article/details/61202084 网上的文章基本上都是只有多数据源或只有动态数据源,而最近的项目需要同时使用两种 ...

  8. SpringBoot 的多数据源配置

    最近在项目开发中,需要为一个使用 MySQL 数据库的 SpringBoot 项目,新添加一个 PLSQL 数据库数据源,那么就需要进行 SpringBoot 的多数据源开发.代码很简单,下面是实现的 ...

  9. springboot mybatis 多数据源配置

    首先导入mybatis等包,这里就不多说. 下面是配置多数据源和mybatis,每个数据源对应一套mybatis模板 数据源1: package com.aaaaaaa.config.datasour ...

  10. 记springboot + MP +Hikari动态数据源配置

    环境准备: springboot 2.1.6 mybatis-plus 数据库驱动 boot 自带hikari驱动 步骤1:  导入多数据源启动工具类 <!-- 多数据源支持 -->< ...

随机推荐

  1. docker中安装的mysql无法远程连接问题解决

    背景: 在ubuntu1804的docker中安装了mysql,版本是5.7.34.因为有复杂的数据要插入到数据库中,所以从宿主机通过pycharm和DBeaver连接,但是一直报错: Can not ...

  2. python版本升级到3.8以及安装虚拟环境

    linux系统升级python版本看起来复杂,如果知道其中步骤也不会觉得很困难.下面记录在deepin系统 15.11和ubuntu16.04系统下将python从2.7升级到3.8过程 升级pyth ...

  3. 不使用kvm的qemu虚拟化

    本文记录的是在某些机器上并不支持kvm虚拟化,单纯使用qemu来完成虚拟机的创建和管理. 系统版本:centos 7 qemu版本:4.2 首先说明一下qemu和kvm的关系: qemu 是一个模拟器 ...

  4. termius macos 破解版,激活版下载,永久激活,亲测可用

    termius 是一款非常值得推荐的 SSH/SFTP 跨平台终端工具,其十分亮眼的功能是可以上传文件夹,这是其他几款终端工具都不具备的,比如说 macOS 自带的终端.号称 21 世纪最强终端的 w ...

  5. DNS--简介&&解析过程

    1 功能 将域名解析为IP地址 基本解决IP难于记忆的问题 也可以将IP地址解析为域名 2 域名 由一连串用点(.)分隔的字符串组成的 标识计算机在网络中的电子方位的 3 存储位置 linux /et ...

  6. Java 中初始化 List 的五种方法

    1.构造 List 后使用 List.add 初始化 1 List<String> stringList = new LinkedList<>(); 2 stringList. ...

  7. vue-element-admin完整开源项目介绍、本地部署和初步探索

    1 说明: 1.1 从一个广受好评的开源项目:vue-element-admin的本地化,来初步分析vue的相关知识. 2 github地址和下载本地部署: https://github.com/Pa ...

  8. Linux复制安装 jdk 环境

    转载请注明出处: 最近在弄服务器环境,发现可以通过复制已安装 jdk 的服务器配置到新的服务器,并配置服务器环境变量配置文件就可以完成. 操作步骤如下: 1. 查看以安装jdk服务器的环境配置,并复制 ...

  9. 11-verilog-有限状态机

    有限状态机 写RTL的时候,实现一个功能的时候有很多种方法 将系统划分为多个状态,状态之间有状态的转移,第一步,第二步......形成有限状态机 流水线技术设计,从输入到输出有多个步骤,多个步骤可以并 ...

  10. JMS微服务架构 - 关于事务提交失败,自动重新提交的机制

    用JMS编写的微服务,由调用端决定了各个微服务执行时,是否需要保持事务的一致性. 也就是RemoteClient在调用微服务方法前,先调用BeginTransaction明确后面所调用的微服务需要保持 ...