springboot动态多数据源

时间:2023-02-24 17:57:07

参考文章:https://www.cnblogs.com/hehehaha/p/6147096.html

前言

目标是springboot工程支持多个MySQL数据源,在代码层面上,同一个SQL(Mapper)可以在多个数据源灵活使用,也就是所说的动态。

这种动态是通过LocalThread实现的,即一个web请求对应一个线程,在线程中指定一个数据源。

1、maven pom

pom.xml里有springboot的starter和数据库驱动,我这里用的是druid

<?xml version="1.0" encoding="UTF-8"?>
<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.ivan.studio</groupId>
<artifactId>fantacy</artifactId>
<version>1.0-SNAPSHOT</version> <properties>
<!-- 文件拷贝时的编码 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- 编译时的编码 -->
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<jdk.version>1.7</jdk.version>
<hive.version>1.2.1</hive.version>
<hadoop.version>2.7.2</hadoop.version>
<shiro.version>1.4.0</shiro.version>
</properties>
<!-- 继承父包 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
</parent> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>1.4.3.RELEASE</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
<!--<dependency>-->
<!--<groupId>org.apache.tomcat</groupId>-->
<!--<artifactId>tomcat-jdbc</artifactId>-->
<!--</dependency>-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency> </dependency>
<!-- druid阿里巴巴数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.15</version>
</dependency>
</dependencies> </project>

2、配置文件

application.properties配置文件里要有主数据源,多数据源,线程池等配置

# 主数据源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ivan?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456 # 更多数据源
custom.datasource.names=ds1,ds2 custom.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds1.url=jdbc:mysql://127.0.0.1:3306/vicky?useUnicode=true&characterEncoding=utf-8
custom.datasource.ds1.username=root
custom.datasource.ds1.password=123456 custom.datasource.ds2.type=com.alibaba.druid.pool.DruidDataSource
custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver
custom.datasource.ds2.url=jdbc:mysql://127.0.0.1:3306/ivan?useUnicode=true&characterEncoding=utf-8
custom.datasource.ds2.username=root
custom.datasource.ds2.password=123456 #连接池的配置信息
spring.datasource.initialSize=10
spring.datasource.minIdle=10
spring.datasource.maxActive=100
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=true
spring.datasource.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.filters=stat,wall,log4j
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

3、动态数据源路由类

动态数据源能进行自动切换的核心就是spring底层提供了AbstractRoutingDataSource类进行数据源的路由的,我们主要继承这个类,实现里面的方法即可实现我们想要的,这里主要是实现方法:determineCurrentLookupKey(),而此方法只需要返回一个数据库的名称即可,所以我们核心的是有一个类来管理数据源的线程池,这个类才是动态数据源的核心处理类。还有另外就是我们使用aop技术在执行事务方法前进行数据源的切换。所以这里有几个需要编码的类,具体如下:

import java.util.ArrayList;
import java.util.List; /**
* @Author: ivan
* @Description:
* @Date: Created in 15:23 18/5/24
* @Modified By:
*/ public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static List<String> dataSourceIds = new ArrayList<String>(); public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
} public static String getDataSourceType() {
return contextHolder.get();
} public static void clearDataSourceType() {
contextHolder.remove();
} /**
* 判断指定DataSrouce当前是否存在
*
* @param dataSourceId
* @return
* @author ivan
* @create 2016年1月24日
*/
public static boolean containsDataSource(String dataSourceId){
return dataSourceIds.contains(dataSourceId);
}
}

数据源路由类:

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

/**
* 动态多数据源
* @Author: ivan
* @Description:
* @Date: Created in 15:22 18/5/24
* @Modified By:
*/ public class DynamicDataSource extends AbstractRoutingDataSource { @Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
} }

指定数据源注解类>com.kfit.config.datasource.dynamic.TargetDataSource:

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; /**
* @Author: ivan
* @Description:
* @Date: Created in 15:29 18/5/24
* @Modified By:
*/ @Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name();
}

切换数据源Advice:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; /**
* @Author: ivan
* @Description:
* @Date: Created in 15:25 18/5/24
* @Modified By:
*/ /**
* 切换数据源Advice
*
* @author ivan
*/
@Aspect
@Order(-10)// 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class); @Before("@annotation(ds)")
public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {
String dsId = ds.name();
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
logger.error("ds=[{}]not exist,use main DataSource > {}", ds.name(), point.getSignature());
} else {
logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(ds.name());
}
} @After("@annotation(ds)")
public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
} }

4、注册多数据源

以上都是动态数据源在注入的时候使用的代码,其实很重要的一部分代码就是注册我们在application.properties配置的多数据源,这才是重点,这里我们使用

ImportBeanDefinitionRegistrar进行注册,具体的代码如下:

package com.ivan.studio.fantacy.common;

/**
* @Author: ivan
* @Description:
* @Date: Created in 15:27 18/5/24
* @Modified By:
*/
import com.alibaba.druid.pool.DruidDataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.bind.RelaxedDataBinder;
import org.springframework.boot.bind.RelaxedPropertyResolver;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata; import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map; /**
* 动态数据源注册<br/>
* 启动动态数据源请在启动类中(如SpringBootSampleApplication)
* 添加 @Import(DynamicDataSourceRegister.class)
*
* @author ivan
*/
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues; // 如配置文件中未指定数据源类型,使用该默认值
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
// private static final Object DATASOURCE_TYPE_DEFAULT =
// "com.zaxxer.hikari.HikariDataSource"; // 数据源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>(); @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 将主数据源添加到更多数据源中
targetDataSources.put("dataSource", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
// 添加更多数据源
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.dataSourceIds.add(key);
} // 创建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition); logger.info("Dynamic DataSource Registry");
} /**
* 创建DataSource
* @return
* @author ivan
* @create 2016年1月24日
*/
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String, Object> dsMap) {
try {
Object type = dsMap.get("type");
if (type == null) {
type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
} Class<? extends DataSource> dataSourceType;
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type); String driverClassName = dsMap.get("driver-class-name").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString(); DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
.username(username).password(password).type(dataSourceType);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
} public DataSource getDataSource(Map<String, Object> dsMap) { DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(dsMap.get("url").toString());
datasource.setUsername(dsMap.get("username").toString());
datasource.setPassword(dsMap.get("password").toString());
datasource.setDriverClassName(dsMap.get("driver-class-name").toString()); return datasource; } /**
* 加载多数据源配置
*/
@Override
public void setEnvironment(Environment env) {
initDefaultDataSource(env);
initCustomDataSources(env);
} /**
* 初始化主数据源
*
* @author ivan
*/
private void initDefaultDataSource(Environment env) {
// 读取主数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map<String, Object> dsMap = new HashMap<String, Object>();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password")); defaultDataSource = getDataSource(dsMap); dataBinder(defaultDataSource, env);
} /**
* 为DataSource绑定更多数据
*
* @param dataSource
* @param env
* @author ivan
*/
private void dataBinder(DataSource dataSource, Environment env){
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
//dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);//false
dataBinder.setIgnoreInvalidFields(false);//false
dataBinder.setIgnoreUnknownFields(true);//true
if(dataSourcePropertyValues == null){
Map<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map<String, Object> values = new HashMap<String, Object>(rpr);
// 排除已经设置的属性
values.remove("type");
values.remove("driver-class-name");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
} /**
* 初始化更多数据源
*
* @author ivan
* @create 2016年1月24日
*/
private void initCustomDataSources(Environment env) {
// 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
for (String dsPrefix : dsPrefixs.split(",")) {
Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = getDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
} }

这里还有一个步骤很重要,由于我们是使用的ImportBeanDefinitionRegistrar的方式进行注册的,所以我们需要在App.java类中使用@Import进行注册,具体改造之后的App.java代码如下:

import com.ivan.studio.fantacy.common.DynamicDataSourceRegister;
import com.ivan.studio.fantacy.service.AutowiredService;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement; import static org.springframework.boot.SpringApplication.run; /**
* @author ivan
*/
@SpringBootApplication
@Import({DynamicDataSourceRegister.class})
public class WebApplication { private static volatile boolean IS_REGISTRY = false; public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}

springboot动态多数据源的更多相关文章

  1. springboot动态多数据源切换

    application-test.properties #datasource -- mysql multiple.datasource.master.url=jdbc:mysql://localho ...

  2. SpringBoot动态数据源

    1.原理图 2.创建枚举类 /** * 存数据源key值 */ public enum DataSourceKey { master,salve,migration } 3.创建自定义注解类 /** ...

  3. SpringBoot运行时动态添加数据源

    此方案适用于解决springboot项目运行时动态添加数据源,非静态切换多数据源!!! 一.多数据源应用场景: 1.配置文件配置多数据源,如默认数据源:master,数据源1:salve1...,运行 ...

  4. SpringBoot多数据源动态切换数据源

    1.配置多数据源 spring: datasource: master: password: erp_test@abc url: jdbc:mysql://127.0.0.1:3306/M201911 ...

  5. SpringBoot与动态多数据源切换

      本文简单的介绍一下基于SpringBoot框架动态多数据源切换的实现,采用主从配置的方式,配置master.slave两个数据库. 一.配置主从数据库 spring: datasource: ty ...

  6. SpringBoot 动态切换多数据源

    1. 配置文件application-dev.properties 2. 动态切换数据源核心 A. 数据源注册器 B. 动态数据源适配器 C. 自定义注解 D. 动态数据源切面     E. 数据源路 ...

  7. Springboot 实现前台动态配置数据源 (修改数据源之后自动重启)

    1.将 db.properties 存放在classpath路径; driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3 ...

  8. 在使用 Spring Boot 和 MyBatis 动态切换数据源时遇到的问题以及解决方法

    相关项目地址:https://github.com/helloworlde/SpringBoot-DynamicDataSource 1. org.apache.ibatis.binding.Bind ...

  9. springboot &plus; mybatis &plus; 多数据源

    此文已由作者赵计刚薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验 在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据源. 代码结构: 简要原理: 1) ...

随机推荐

  1. ueditor 发布到服务器提示&OpenCurlyDoubleQuote;后端配置项没有正常加载,上传插件不能正常使用!”

    原来是发布后缺少文件,直接从本地的复制过去就好了.

  2. &lbrack;Angular2 Router&rsqb; Style the Active Angular 2 Navigation Element with routerLinkActive

    You can easily show the user which route they are on using Angular 2’s routerLinkActive. Whenever a ...

  3. Hadoop2

    http://www.cnblogs.com/miaoxiaoyu/archive/2012/07/29/2614060.html

  4. 介绍一下Spring Cloud Config

    Spring Cloud Config为分布式系统中的外部配置提供服务器和客户端支持.使用Config Server,您可以在所有环境中管理应用程序的外部属性.客户端和服务器上的概念映射与Spring ...

  5. CentOS7基本配置一

    CentOS7基本配置一 安装VMwareTools 1.点击重新安装VM-tool, 继而找到压缩文件VMwareTools-10.2.0...tar.gz,复制到桌面下,解压这么压缩文件到桌面下 ...

  6. &lbrack;福大软工&rsqb; Z班 第4次成绩排行榜

    作业要求 http://www.cnblogs.com/easteast/p/7511234.html 评分细则 (1)博客--15分,分数组成如下: 随笔开头,给出结队两个同学的学号.PS:结对成员 ...

  7. git 变基&lpar;无卵用&rpar;

    在当前分支执行rebase即可,会将提交的记录变成一条直线 git rebase

  8. MySQL Yum存储库 安装、升级、集群

    添加MySQL Yum存储库 首先,将MySQL Yum存储库添加到系统的存储库列表中.按着这些次序: 在http://dev.mysql.com/downloads/repo/yum/上转到MySQ ...

  9. Software Testing Techniques Homework 2

    Problem 1 1. The fault is i > 0, it should be  i >= 0, because if the case is x = [0], y= 0, w ...

  10. vsftpd安装配置虚拟用户

    原文发表于cu:2016-03-11 参考文档: FTP原理:http://vbird.dic.ksu.edu.tw/linux_server/0410vsftpd_1.php FTP配置:http: ...