在项目开发的过程中,有时我们有这样的需求,需要去调用别的系统中的数据,那么这个时候系统中就存在多个数据源了,那么我们如何来解决程序在运行的过程中到底是使用的那个数据源呢?

假设我们系统中存在2个数据源 mysql 和 oracle. 系统中存在 2个方法  methodA 和 methodB ,其中methodA是是需要去调用mysql、methodB是需要使用到oracle,那么在我们调用methodA或methodB时是如何知道使用的是mysql还是oracle呢? 我的做法是  使用Spring 的 动态数据源路由来解决这个问题。

需求: 系统中要显示一些图表信息,但是图表的数据是来自别的数据库,不是自己系统使用的数据库,如何来解决这个问题。

解决方案:在程序运行的过程中动态的去决定使用哪种数据源,借助Spring 的抽象数据源路由来解决。

前置条件:

1、@Primary 表示在程序中存在同一种类型的bean有多个时,默认使用有@Primay 注解标注的

2、当产生了Connection后,事务可能就会开启了

3、如果要获取到一个Connection,那么我们需要在事务开启前知道使用的是那个数据源,如果我们要写切面去判断使用那个数据源,那么一定要在事务切面之前

4、如果一个变量要在程序的任何地方都可以获取到,那么可以使用ThreadLocal来存放这个变量,如果存放在static类型的变量中,那么会存在线程安全问题。

解决思路:

1、在Spring 中提供了一个类  AbstractRoutingDataSource 这个类中有一个determineCurrentLookupKey() 方法,在这个方法中返回一个 key ,那么Spring 就知道使用那个数据源。那么Spring 怎么知道这个你返回的 key 是什么意思呢? 那么在这个类中必然有个方法 可以让 key 和 数据源 进行关联即 setTargetDataSources(Map<Object, Object> targetDataSources) 方法,在这个方法中的参数中传递一个map ,可以让map 的 key  为 determineCurrentLookupKey() 需要的key ,值为具体的数据源。

2、那么我们什么时候需要设置 determineCurrentLookupKey() 方法的key呢?那么肯定是那个方法需要切换数据源,即此时我们需要向ThreadLocal中设置当前数据源的key,那么假如我们有很多方法需要切换数据源,难道每个方法都要自己手动向ThreadLocal中插入值吗?这个代码太冗余了,那么我们此时就可以自定义一个注解,然后写一个切面凡是带这个注解的方法都进行拦截,然后再在这个切面中设置数据源需要的key.

3、那么我们的切面什么时候执行呢?我们知道事务是有Connection开启的,如果我们的切面在事务之后执行,那么什么意义也没有,即数据源切换失败。所以我们自己写的切面需要在Spring 的事务切面之前执行,使用@Order注解执行切面的执行顺序,注解里面的值越小越先执行。

4、我们知道我们的事务开启需要数据源,或Jpa操作数据库也需要数据源,那么我们应该将那个数据源作为主数据源呢?是我们系统的数据源还是第三方系统的数据源,这个肯定不是,应该有程序运行时动态决策,因此就需要一个 实现了 AbstractRoutingDataSource 的类作为主数据源。

具体步骤:

一、引入pom.xml文件

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.huan.springboot</groupId>
<artifactId>springboot_20_multi_datasource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> <name>springboot_20_multi_datasource</name>
<url>http://maven.apache.org</url> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0.4.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

 二、配置2个数据源

1、配置application.yml文件,向里面增加2个不同数据源的配置

spring.datasource.mysql.url=jdbc:mysql://localhost/information_schema?useUnicode=true&characterEncoding=utf-8
spring.datasource.mysql.username=root
spring.datasource.mysql.password=root
spring.datasource.mysql.driver-class-name=com.mysql.jdbc.Driver spring.datasource.oracle.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
spring.datasource.oracle.username=system
spring.datasource.oracle.password=admin
spring.datasource.oracle.driver-class-name=oracle.jdbc.driver.OracleDriver spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming.strategy=org.hibernate.cfg.ImprovedNamingStrategy

2、编写 MultiDataSourceConfig 配置文件,配置这2个数据源

package com.huan.springboot.config;

import javax.sql.DataSource;

import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* 多数据源配置
*
* @描述
* @作者 huan
* @时间 2018年3月8日 - 下午8:47:30
*/
@Configuration
public class MultiDataSourceConfig { @Bean
@ConfigurationProperties("spring.datasource.mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
} @Bean
@ConfigurationProperties("spring.datasource.oracle")
public DataSource oracleDataSource() {
return DataSourceBuilder.create().build();
} }

 三、编写 自定义注解 + 数据源路由

   1、自定义一个注解 DynamicDataSource

package com.huan.springboot.datasource;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* 动态数据源注解
*
* @描述
* @作者 huan
* @时间 2018年3月8日 - 下午8:55:23
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DynamicDataSource {
/**
* 默认是mysql数据源
*
* @return
*/
DynamicDataSourceTypeEnum value() default DynamicDataSourceTypeEnum.MYSQL;
}

   2、编写一个数据源枚举类,用来表示有哪些数据源,防止手动书写会写错

package com.huan.springboot.datasource;

import lombok.AllArgsConstructor;
import lombok.Getter; /**
* 动态数据源类型枚举
*
* @描述
* @作者 huan
* @时间 2018年3月8日 - 下午8:55:52
*/
@AllArgsConstructor
@Getter
public enum DynamicDataSourceTypeEnum { ORACLE("oracle", "oracle数据源"), //
MYSQL("mysql", "当前使用的是mysql数据源"); private String type;
private String desc;
}

 3、程序在需要切换数据源的方法会将上面的这个枚举类型保存,那么保存到哪里呢,肯定是线程安全的ThreadLocal中,因此需要这个类DynamicDataSourceTypeHolder

package com.huan.springboot.datasource;

/**
* 保存当前线程的数据源类型
*
* @描述
* @作者 huan
* @时间 2018年3月8日 - 下午8:59:09
*/
public class DynamicDataSourceTypeHolder { private static final ThreadLocal<DynamicDataSourceTypeEnum> DATA_SOURCE_TYPE = new ThreadLocal<>(); public static void setDataSourceType(DynamicDataSourceTypeEnum dataSourceTypeEnum) {
DATA_SOURCE_TYPE.set(dataSourceTypeEnum);
} public static DynamicDataSourceTypeEnum getDataSourceType() {
return DATA_SOURCE_TYPE.get();
} public static void clear() {
DATA_SOURCE_TYPE.set(null);
} }

4、当我们在需要切换数据源的方法上编写了 @DynamicDataSource 注解后,就需要有一个类来处理这个注解,因此就有了下面这个类。

package com.huan.springboot.datasource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; /**
* 动态数据源切面
*
* @描述
* @作者 huan
* @时间 2018年3月8日 - 下午9:15:49
*/
@Component
@Aspect
@Order(0)
public class DynamicDataSourceAspect {
@Around("@annotation(dataSource)")
public Object invoked(ProceedingJoinPoint pjp, DynamicDataSource dataSource) throws Throwable {
DynamicDataSourceTypeHolder.setDataSourceType(dataSource.value());
try {
return pjp.proceed();
} finally {
DynamicDataSourceTypeHolder.clear();
}
}
}

   注意:

1、看 @Order里面的值,这个值比较小,会保证这个切面在事务切面之前执行。可以删除这个注解然后看一下效果。

2、这个类中将当前数据源的 key 保存到了 ThreadLocal 类型的变量中

5、有了上面这些内容,那么我们肯定需要 需要编写 动态数据源路由,用于决定是返回那个数据源key ,决定使用的是那个数据源

package com.huan.springboot.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* 动态的数据源路由,由此类决定具体使用的是那个数据源
*
* @描述
* @作者 huan
* @时间 2018年3月8日 - 下午9:01:58
*/
public class DynamicDataSourceRouter extends AbstractRoutingDataSource { @Override
protected Object determineCurrentLookupKey() {
DynamicDataSourceTypeEnum dataSourceType = DynamicDataSourceTypeHolder.getDataSourceType();
if (null == dataSourceType) {
System.out.println("没有获取到数据源,使用默认的数据源.");
return null;
} else {
System.out.println(dataSourceType.getDesc());
return dataSourceType.getType();
}
}

注意:

determineCurrentLookupKey() 这个方法返回了一个key ,那么Spring就知道使用那个数据源。那么Spring是怎么知道的呢 ,看下方的 DynamicDataSourceRoteConfig 配置类,这个类我上方还没有写。

6、上方编写了动态数据源路由,但是数据源的key和具体的数据源还没有进行配置,因此由下方这个配置类来进行配置。

package com.huan.springboot.config;

import java.util.HashMap;
import java.util.Map; import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.transaction.annotation.EnableTransactionManagement; import com.huan.springboot.datasource.DynamicDataSourceRouter;
import com.huan.springboot.datasource.DynamicDataSourceTypeEnum; /**
* 数据源路由配置
*
* @描述
* @作者 huan
* @时间 2018年3月8日 - 下午9:15:02
*/
@Configuration
@EnableTransactionManagement
public class DynamicDataSourceRoteConfig { @Autowired
@Lazy
private DataSource mysqlDataSource;
@Autowired
@Lazy
private DataSource oracleDataSource; @Bean
@Primary
public DataSource dynamicDataSourceRoute() {
DynamicDataSourceRouter dataSourceRouter = new DynamicDataSourceRouter();
// 默认是mysql 数据源
dataSourceRouter.setDefaultTargetDataSource(mysqlDataSource);
// 映射某个key 为具体的数据源
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DynamicDataSourceTypeEnum.MYSQL.getType(), mysqlDataSource);
targetDataSources.put(DynamicDataSourceTypeEnum.ORACLE.getType(), oracleDataSource); dataSourceRouter.setTargetDataSources(targetDataSources);
return dataSourceRouter;
} }

 注意: 在dynamicDataSourceRoute()方法中

1、使用了@Primary注解修饰,那么当需要使用到数据源的时候会注入这个,而不会注入有上方MultiDataSourceConfig配置的2个数据源。

2、在这个方法中设置了一个默认的数据源,即当没有获取到数据源的key时应该使用那个数据源

3、在这个方法中 有一个  dataSourceRouter.setTargetDataSources(targetDataSources); 这句。在这个里面有一个属性 targetDataSources ,可以看到它的key 和具体的数据源进行关联了。

到此动态切换数据源的代码就写完了,那么下方写一个小例子测试一下。

四、示例

1、示例代码

 

2、查看结果

spring、spring boot中配置多数据源的更多相关文章

  1. Spring Boot + Mybatis 配置多数据源

    Spring Boot + Mybatis 配置多数据源 Mybatis拦截器,字段名大写转小写 package com.sgcc.tysj.s.common.mybatis; import java ...

  2. Spring MVC 使用tomcat中配置的数据源

    Spring MVC 使用tomcat中配置的数据源 配置tomcat数据源 打开tomcat目录下的conf目录,编辑sever.xml目录.在<GlobalNamingResources&g ...

  3. 在Spring Boot中配置web app

    文章目录 添加依赖 配置端口 配置Context Path 配置错误页面 在程序中停止Spring Boot 配置日志级别 注册Servlet 切换嵌套服务器 在Spring Boot中配置web a ...

  4. WebSphere中配置的数据源在Web应用中引用的写法

    WebSphere中配置的数据源在Web应用中引用时名称一定要和数据源的JNDI名称保持一致,否则会出现无法找到数据源的错误. 引用WAS的数据源时只需要与JNDI名称保持一致即可. 引用Tomcat ...

  5. spring boot中配置日志log和热部署

    Java的日志有很多 个人强烈不推荐log4j ,推荐log4j2和logback 在高并发,多线程的环境下log4j1 的性能和log4j2相比可以用junk来形容  对就是junk.log4j2的 ...

  6. Spring Boot2 系列教程(八)Spring Boot 中配置 Https

    https 现在已经越来越普及了,特别是做一些小程序或者公众号开发的时候,https 基本上都是刚需了. 不过一个 https 证书还是挺费钱的,个人开发者可以在各个云服务提供商那里申请一个免费的证书 ...

  7. spring中配置jndi数据源

    spring  AplicationContext.xml中的配置 <bean id="dataSource1" class="org.springframewor ...

  8. spring中配置jdbc数据源

    1.加入jdbc驱动器包,mysql-connector-java.jar 2.加入commons-dbcp.jar配置数据源 3.在classpath下新建文件jdbc.properties,配置j ...

  9. Spring基础——在Spring Config 文件中配置 Bean

    一.基于 XML 的 Bean 的配置——通过全类名(反射) <bean <!-- id: bean 的名称在IOC容器内必须是唯一的若没有指定,则自动的将全限定类名作为 改 bean 的 ...

随机推荐

  1. Linux-实战常用命令

    目录 关机/重启/注销 系统信息和性能查看 磁盘和分区 ⽤户和⽤户组 ⽹络和进程管理 常⻅系统服务命令 ⽂件和⽬录操作 ⽂件查看和处理 打包和解压 RPM包管理命令 YUM包管理命令 DPKG包管理命 ...

  2. es6语法中promise的使用方法

    Promise是一个构造函数,它有resolve,reject,race等静态方法;它的原型(prototype)上有then,catch方法,因此只要作为Promise的实例,都可以共享并调用Pro ...

  3. spark相关介绍-提取hive表(一)

    本文环境说明 centos服务器 jupyter的scala核spylon-kernel spark-2.4.0 scala-2.11.12 hadoop-2.6.0 本文主要内容 spark读取hi ...

  4. CodeForce-734C Anton and Making Potions(贪心+二分)

    CodeForce-734C Anton and Making Potions  C. Anton and Making Potions time limit per test 4 seconds m ...

  5. PHP的switch和ifelse谁更快?

    对于多个if条件判断的情况下,我们使用switch来代替ifelse对于代码来说会更加的清晰明了,那么他们的效率对比呢?从PHP手册中发现有人已经对比过了,自己也用他的代码进行了实验: $s = ti ...

  6. php在类中使用回调函数 如array_map

    <?php class foo {   var $var;   function bar() {      array_map(array($this, "baz"), ar ...

  7. 再谈OAuth授权

    工作场景流程 大家都知道OAuth是用于第三方授权的,当你用其他的APP想访问微信账号的昵称.粉丝.联系人等信息,这里就需要微信进行授权,此时在APP的网页端是可以发现有微信登录的,点开会出现弹框,在 ...

  8. P6097-[模板]子集卷积

    正题 题目链接:https://www.luogu.com.cn/problem/P6097 题目大意 长度为\(2^n\)的序列\(a,b\)求一个\(c\)满足 \[c_k=\sum_{i|j=k ...

  9. MySQL学习总结:提问式回顾 undo log 相关知识

    原文链接:MySQL学习总结:提问式回顾 undo log 相关知识 1.redo 日志支持恢复重做,那么如果是回滚事务中的操作呢,也会有什么日志支持么? 也回滚已有操作,那么就是想撤销,对应的有撤销 ...

  10. Google Chrome打开权限设置开关(摄像头,录音等)

    在搜索框输入以下字符 chrome://flags/#unsafely-treat-insecure-origin-as-secure