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

假设我们系统中存在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. Identity用户管理入门四(修改、删除用户)

    修改用户不能修改Id及用户名所以创建视图模型时需要去除,新增用户跟修改用户基本视图一直,所以不再做演示 一.新建UpdateUserViewModel视图模型 using System.Compone ...

  2. Vue状态管理Vuex简单使用

    状态管理保存在store\index.js中,简单说明如下 import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export def ...

  3. 回收Windows 10恢复分区之后的磁盘空间

    我电脑上安装了Windows 10和Linux双系统,现在将Linux删除之后,准备将其磁盘空间并入到Windows 10的C盘中,但是发现C盘跟Linux空间之间还隔了一个Windows的恢复分区, ...

  4. ysoserial CommonsColletions2分析

    ysoserial CommonsColletions2分析 前言 此文章是ysoserial中 commons-collections2 的分析文章,所需的知识包括java反射,javassist. ...

  5. PTA 面向对象程序设计 6-1 引用作函数形参交换两个整数

    引用作函数形参交换两个整数 设计一个void类型的函数Swap,该函数有两个引用类型的参数,函数功能为实现两个整数交换的操作. 裁判测试程序样例: #include <iostream> ...

  6. 解析Markdown文件生成React组件文档

    前言 最近做的项目使用了微前端框架single-spa. 对于这类微前端框架而言,通常有个utility应用,也就是公共应用,里面是各个子应用之间可以共用的一些公共组件或者方法. 对于一个团队而言,项 ...

  7. 深入学习Composer原理(四)

    本系列第四篇文章,也是最后一篇 首先,我们先看看Composer的源码从哪里看起.当然,请您先准备好源码. composer init或者直接install之后,自动生成了一个vendor目录,这时您 ...

  8. Python - 生成requirement.text 文件

    前言 该篇操作笔记摘自小菠萝 Python项目中,一般都会有一个 requirements.txt 文件 这个文件主要是用于记录当前项目下的所有依赖包及其精确的版本号,以方便在一个新环境下更快的进行部 ...

  9. Linux系列(35) - 光盘yum源搭建(2)

    光盘搭建yum源 背景 当前Linux服务器没有网络,yum源下载好了,在光盘中 step-01 挂载光盘 mkdir /mnt/cdrom #建立挂载点 mount /dev/cdrom /mnt/ ...

  10. (数据科学学习手札128)在matplotlib中添加富文本的最佳方式

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 长久以来,在使用matplotlib进行绘 ...