Spring Boot + Druid 多数据源绑定
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.java 和 TargetDataSource.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 多数据源绑定的更多相关文章
- spring boot + druid + mybatis + atomikos 多数据源配置 并支持分布式事务
文章目录 一.综述 1.1 项目说明 1.2 项目结构 二.配置多数据源并支持分布式事务 2.1 导入基本依赖 2.2 在yml中配置多数据源信息 2.3 进行多数据源的配置 三.整合结果测试 3.1 ...
- Spring Boot配置多数据源并实现Druid自动切换
原文:https://blog.csdn.net/acquaintanceship/article/details/75350653 Spring Boot配置多数据源配置yml文件主数据源配置从数据 ...
- Spring Boot与多数据源那点事儿~
持续原创输出,点击上方蓝字关注我 目录 前言 写这篇文章的目的 什么是多数据源? 何时用到多数据源? 整合单一的数据源 整合Mybatis 多数据源如何整合? 什么是动态数据源? 数据源切换如何保证线 ...
- spring boot项目自定义数据源,mybatisplus分页、逻辑删除无效解决方法
Spring Boot项目中数据源的配置可以通过两种方式实现: 1.application.yml或者application.properties配置 2.注入DataSource及SqlSessio ...
- 21. Spring Boot Druid 数据源配置解析
1.数据源配置属性类源码 package org.springframework.boot.autoconfigure.jdbc; @ConfigurationProperties( prefix = ...
- spring boot druid mybatis多数据源
一.关闭数据源自动配置(很关键) @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) 如果不关闭会报异常:o ...
- spring boot 配置双数据源mysql、sqlServer
背景:原来一直都是使用mysql数据库,在application.properties 中配置数据库信息 spring.datasource.url=jdbc:mysql://xxxx/test sp ...
- spring boot mybatis多多数据源解决方法
在我们的项目中不免会遇到需要在一个项目中使用多个数据源的问题,像我在得到一个任务将用户的聊天记录进行迁移的时候,就是用到了三个数据源,当时使用的AOP的编程方式根据访问的方法的不同进行动态的切换数据源 ...
- spring boot:shardingsphere多数据源,支持未分表的数据源(shardingjdbc 4.1.1)
一,为什么要给shardingsphere配置多数据源? 1,shardingjdbc默认接管了所有的数据源, 如果我们有多个非分表的库时,则最多只能设置一个为默认数据库, 其他的非分表数据库不能访问 ...
随机推荐
- LR Robust Stereo VIO for Fast Autonomous Flight
Abstract 我们展示说我们的Stereo MSCKF在算力上跟state-of-the-art的单目方案是可比的, 而且提供了很大的鲁棒性. 1. Introduction 贡献 第一个开源的f ...
- java Synchronized集合
在Collections存在相关"Synchronized"支持同步的集合, 在java1.0 也存在"Vector"; 为什么会选择放弃"Vecto ...
- API测试-接口测试基础(1)
由于自己想学习API方面的测试,但是市面上搜不到相关的图书可以系统学习,网上的内容又零零散散,适合有点API开发基础的人去搜索.为了方面新手学习API测试,现在整理了他人的宝贵经验和自己的学习心得,尽 ...
- kubernetes部署Percona XtraDB Cluster集群
PXC介绍 全称percona-xtradb-cluster,提供了MySQL高可用的一种实现方法.PXC集群以节点组成(推荐至少3节点,便于故障恢复),每个节点都是基于常规的 MySQL Serve ...
- Flutter学习五之网络请求和轮播图的实现
上期讲到了,怎样实现一个下拉刷新和加载更多的列表,数据更新,需要使用到网络请求,在flutter中,怎样实现一个网络请求呢?官方使用的是dart io中的HttpClient发起的请求,但HttpCl ...
- 静态代理、jdk动态代理、cglib动态代理
一.静态代理 Subject:抽象主题角色,抽象主题类可以是抽象类,也可以是接口,是一个最普通的业务类型定义,无特殊要求. RealSubject:具体主题角色,也叫被委托角色.被代理角色.是业务逻辑 ...
- spring整合(Junit、web)
1.整合Junit (1)整合前的测试类代码 public class Test { public static void main(String[] args) { ApplicationConte ...
- spring给容器注册组件 的几种方式
环境搭建: 新建一个maven项目,引入依赖 <dependency> <groupId>org.springframework</groupId> <art ...
- 你在开发过程中使用Git Rebase还是Git Merge?
摘要:在git里面经常的一个争论是到底用rebase还是用merge? 1. 痛苦吗?代码历史中的迷失羔羊 我们先来看一个真实的代码提交历史图形化截图: 图片源自 https://storage.kr ...
- Go-项目环境搭建
Go-环境搭建两个参数 1. GOROOT Go SDK安装目录,Go语言安装目录 2. GOPATH 项目根目录 Go-项目目录结构 goProjectName 项目根目录 bin 项目编译后 ...