date: 2019-12-19 14:40:00

updated: 2019-12-19 15:10:00

Spring Boot + Druid 多数据源绑定

版本环境:Spring Boot 2.0.6

参考文档地址:

https://blog.csdn.net/cllaure/article/details/81509303

1. 项目结构

列举个几个比较重要的文件,其他的文件进行了省略

  • analysis

    • AnalysisApplication.java
    • SpringUtil.java
  • dao
    • OperateLogMapper.java
  • datasource
    • DynamicDataSource.java
    • DynamicDataSourceAspect.java
    • DynamicDataSourceContextHolder.java
    • DynamicDataSourceRegister.java
    • TargetDataSource.java
  • entity
  • service
    • AnalysisService.java
  • utils

2. 关于启动类 AnalysisApplication

因为我想要的是在启动该项目时,不通过访问 url 的方式来激活某个接口,而是让程序自动来执行某个方法,所以需要在启动类中使用 SpringUtil.getApplicationContext() 来获取 Spring 上下文,从而将你需要的那个类注入进来,并执行该来的方法

@SpringBootApplication
@Import(DynamicDataSourceRegister.class) // 重点!!!用来覆写 Spring 默认创建数据库连接方法
@ComponentScan("com.bigdata.*") // Service层的注解是@component,但是有可能会访问不到,在这里通过注解的方式直接将整个目录都引入进来
@MapperScan("com.bigdata.dao")
public class AnalysisApplication {
public static void main(String[] args) {
SpringApplication.run(AnalysisApplication.class, args); // 当 Spring 启动后会获取到 analysis 实例,并执行 appStart() 方法
ApplicationContext context = SpringUtil.getApplicationContext();
AnalysisService analysis = context.getBean(AnalysisService.class);
analysis.appStart();
}
}

3. 关于 SpringUtil

需要注意的是,此类需要放到启动类同包或者子包下才能被扫描,否则失效。

@Component
public class SpringUtil implements ApplicationContextAware { private static ApplicationContext applicationContext = null; @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringUtil.applicationContext == null){
SpringUtil.applicationContext = applicationContext;
}
} //获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
} //通过name获取 Bean.
public static Object getBean(String name){
return getApplicationContext().getBean(name); } //通过class获取Bean.
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
} //通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}

4. 多数据源连接初始化

首先创建一个 datasource 的文件夹,该文件夹下需要的文件有:

  • DynamicDataSource.java
  • DynamicDataSourceAspect.java
  • DynamicDataSourceContextHolder.java
  • DynamicDataSourceRegister.java
  • TargetDataSource.java

如果是想通过注解的方法来切换数据源,那么需要用到 DynamicDataSourceAspect.javaTargetDataSource.java,反之如果你的数据源连接特别多,要通过传参来切换数据源,那么就不需要这两个文件。

下面我会按照多数据源连接为例,改写文档上的代码。

4.1 DynamicDataSource.java

这个文件的目的是覆写 determineCurrentLookupKey() 方法,返回的值是保存在 DynamicDataSourceContextHolder 类中当前数据库连接

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

4.2 DynamicDataSourceContextHolder.java

该文件是用来切换数据源,通过 setDataSourceType(String dataSourceType) 方法来切换到指定的数据库连接,其中 dataSourceType 其实是在初始化数据库连接时的别名。

dataSourceIds 里保存了所有数据库连接的别名。

public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
public static List<String> dataSourceIds = new ArrayList<>(); public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
} public static String getDataSourceType() {
return contextHolder.get();
} // 恢复至默认的数据库
public static void clearDataSourceType() {
contextHolder.remove();
} /**
* 判断指定DataSrouce当前是否存在
*/
public static boolean containsDataSource(String dataSourceId) {
return dataSourceIds.contains(dataSourceId);
}
}

4.3 DynamicDataSourceRegister.java

该文件用来初始化数据源连接,并注入到 Bean 中。

Spring 启动前,会自动执行 setEnvironment(Environment env) 方法,在这个方法体通过读取配置文件,来初始化数据库连接。由于我们需要通过别名来切换数据库连接,所以如果在库名唯一的情况下,我们可以通过数据库名来作为别名。

/**
* 动态数据源注册
* 启动动态数据源请在启动类中 添加 @Import(DynamicDataSourceRegister.class)
*/ public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class); // 如配置文件中未指定数据源类型,使用该默认值
private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource"; // 数据源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = new HashMap<>(); //加载多数据源配置
@Override
public void setEnvironment(Environment env) {
// 读取配置文件获取更多数据源
PropertiesUtil props = new PropertiesUtil("relations.properties");
PropertiesEntity propsEntity = PropertiesEntity.newInstance();
...... 解析配置文件并赋值 ...... System.out.println("开始初始化动态数据源~~~~");
initCustomDataSources(propsEntity);
} //初始化更多数据源
private void initCustomDataSources(PropertiesEntity propsEntity) {
Map<String, Object> dsMap = new HashMap<>(); // 通过循环来实现多个数据库连接的初始化
dsMap.put("driver-class-name", propsEntity.getDriveClass());
dsMap.put("url", propsEntity.getUrl());
dsMap.put("username", propsEntity.getUsername());
dsMap.put("password", propsEntity.getPassword());
DataSource ds = buildDataSource(dsMap); // 假设库名为 "db1", 将其设置为默认数据库连接,当然也可以不设置,这个无所谓,在 `registerBeanDefinitions` 方法中将默认数据库连接的别名绑定为 default
if(dbName.equals("db1")){
defaultDataSource = buildDataSource(dsMap);
}else{
customDataSources.put(dbName, ds);
}
} @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
// 将主数据源添加到更多数据源中
targetDataSources.put("default", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("default");
// 添加更多数据源
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("default", beanDefinition); logger.info("Dynamic DataSource Registry"); } //创建DataSource
@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;
}
}

4.4 DynamicDataSourceAspect.java

这个文件主要用来编写在引用了注解的方法执行前后所需要做的操作。

/**
* 切换数据源Advice
*/
@Aspect
@Order(-1)// 保证该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) {
String dsId = ds.name();
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());
}else {
logger.debug("Use DataSource : {} > {}", dsId, point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(dsId);
} }
@After("@annotation(ds)")
public void restoreDataSource(JoinPoint point, TargetDataSource ds) {
logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}

4.5 TargetDataSource.java

注解文件

// 在方法上使用,用于指定使用哪个数据源
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String name();
}

5. 方法实现

如果是通过注解实现,可以在此文件中的方法或者dao层中的方法上使用注解,并通过对 name 进行赋值来切换数据库,如果没有值的话使用的是默认数据库连接。

@Component
public class AnalysisService { @Autowired
OperateLogMapper opLogMapper; // @TargetDataSource(name="")
public void appStart() {
// 切换到别名为 db1 的数据库连接
DynamicDataSourceContextHolder.setDataSourceType("db1");
... 数据库操作 ...
// 恢复至默认连接
DynamicDataSourceContextHolder.clearDataSourceType(); // 通过别名切换到默认连接,如果不通过 set 方法直接进行数据库操作,也是在默认连接进行的
DynamicDataSourceContextHolder.setDataSourceType("default");
... 数据库操作 ...
}
}

6. 关于 MyBatis.xml

使用 MyBatis 来编写 sql 语句时,将表名通过变量传递。此时如果用 #{0},会报错,因为 # 会把里面的参数自动加上单引号然后拼接到字符串上,如果是对于类似 where name = '张三' 是没有问题的,但是对于 select * from 'tableName' 是有问题的。

有两个解决方法,一个是将需要传递的参数打包成 HashMap,在赋值的时候使用 select * from ${_key}, 这样就可以动态赋值了。第二个方法是传递N个参数,使用 select * from ${param1} 来进行赋值。

Spring Boot + Druid 多数据源绑定的更多相关文章

  1. spring boot + druid + mybatis + atomikos 多数据源配置 并支持分布式事务

    文章目录 一.综述 1.1 项目说明 1.2 项目结构 二.配置多数据源并支持分布式事务 2.1 导入基本依赖 2.2 在yml中配置多数据源信息 2.3 进行多数据源的配置 三.整合结果测试 3.1 ...

  2. Spring Boot配置多数据源并实现Druid自动切换

    原文:https://blog.csdn.net/acquaintanceship/article/details/75350653 Spring Boot配置多数据源配置yml文件主数据源配置从数据 ...

  3. Spring Boot与多数据源那点事儿~

    持续原创输出,点击上方蓝字关注我 目录 前言 写这篇文章的目的 什么是多数据源? 何时用到多数据源? 整合单一的数据源 整合Mybatis 多数据源如何整合? 什么是动态数据源? 数据源切换如何保证线 ...

  4. spring boot项目自定义数据源,mybatisplus分页、逻辑删除无效解决方法

    Spring Boot项目中数据源的配置可以通过两种方式实现: 1.application.yml或者application.properties配置 2.注入DataSource及SqlSessio ...

  5. 21. Spring Boot Druid 数据源配置解析

    1.数据源配置属性类源码 package org.springframework.boot.autoconfigure.jdbc; @ConfigurationProperties( prefix = ...

  6. spring boot druid mybatis多数据源

    一.关闭数据源自动配置(很关键) @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) 如果不关闭会报异常:o ...

  7. spring boot 配置双数据源mysql、sqlServer

    背景:原来一直都是使用mysql数据库,在application.properties 中配置数据库信息 spring.datasource.url=jdbc:mysql://xxxx/test sp ...

  8. spring boot mybatis多多数据源解决方法

    在我们的项目中不免会遇到需要在一个项目中使用多个数据源的问题,像我在得到一个任务将用户的聊天记录进行迁移的时候,就是用到了三个数据源,当时使用的AOP的编程方式根据访问的方法的不同进行动态的切换数据源 ...

  9. spring boot:shardingsphere多数据源,支持未分表的数据源(shardingjdbc 4.1.1)

    一,为什么要给shardingsphere配置多数据源? 1,shardingjdbc默认接管了所有的数据源, 如果我们有多个非分表的库时,则最多只能设置一个为默认数据库, 其他的非分表数据库不能访问 ...

随机推荐

  1. 中间件、蓝图、g对象

    中间件 ''' flask中一旦请求到来,要执行app()--->>>执行的是app.__call__,整个flask的入口 ''' from flask import Flask ...

  2. vue中axios的使用

    新开了一个vue项目,从头到尾都是一个人做的,所以就自己配置了一个axios.js文件 第一种方法.使用axios 需要下载安装   npm install axios,下载完成之后 在main.js ...

  3. Java中synchronized关键字理解

    好记性不如烂笔头~~ 并发编程中synchronized关键字的地位很重要,很多人都称它为重量级锁.利用synchronized实现同步的基础:Java中每一个对象都可以作为锁.具体表现为以下三种形式 ...

  4. Centos-shell-简介

    shell 壳 1. 用户在操作系统上完成的所有任务都是通过shell与linux内核的交互实现的, 是用户和操作系统内核之间的通信桥梁 用户操作任务 <__> shell <__& ...

  5. Regression trees树回归 以及其他

    https://www.cnblogs.com/wuliytTaotao/p/10724118.html 选 weighted variance 最小的 但是weighted variance是怎么计 ...

  6. Python练习题 039:Project Euler 011:网格中4个数字的最大乘积

    本题来自 Project Euler 第11题:https://projecteuler.net/problem=11 # Project Euler: Problem 10: Largest pro ...

  7. javascript内置对象的innerText、innerHTML、join方法的认识

    innerText语法规范:HTMLElement.innerText = string ;//后面的赋值是一个字符串形式 innerText是一个非标准形式,不识别HTML标签 返回值会去除空格和换 ...

  8. Windows 无法验证此设备所需的驱动程序的数字签名”的问题

    转载: 1.https://jingyan.baidu.com/article/375c8e19c2b25b25f2a229a3.html 2. https://jingyan.baidu.com/a ...

  9. 满屏的try-catch,不瘆得慌?

    持续原创输出,点击上方蓝字关注我 目录 前言 Spring Boot 版本 全局统一异常处理的前世今生 Spring Boot的异常如何分类? 如何统一异常处理? 异常匹配的顺序是什么? 总结 前言 ...

  10. ansible-playbook流程控制-when条件判断

    1. ansible-playbook添加判断     when相当于shell脚本里的if 判断,when语句就是用来实现这个功能的,它是一个jinja2的语法,但是不需要双大括号,用法很简单  1 ...