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默认接管了所有的数据源, 如果我们有多个非分表的库时,则最多只能设置一个为默认数据库, 其他的非分表数据库不能访问 ...
随机推荐
- Gradle系列之Android Gradle高级配置
本篇文章主要在之前学习的基础上,从实际开发的角度学习如何对 Android Gradle 来进行自定义以满足不同的开发需求,下面是 Gradle 系列的几篇文章: Gradle系列之初识Gradle ...
- Unity 如何在窗口大小可以随意改变的情况下让游戏世界完整的显示在镜头中
当我们开发游戏时,如果是开发手机游戏,屏幕窗口的比例是固定的,不会说在运行时改变的. 但是,PC端的游戏就不一定,我希望它能被用户随意拉扯,但完整的内容还是能显示出来,这里我直接放例子: 请注意黑色的 ...
- 使用SpringBoot的方式配置过滤器
springboot 不存在web.xml 那么如何配置过滤器呢 springboot提供了一种方式 通过spring容器配置 @Bean public FilterRegistrationBean ...
- vulnhub-Os-hackNos-3
vulnhub-Os-hackNos-3 nmap 192.168.114.0/24 查看存活主机,发现192.168.114.142. 具体探究: 发现80和22端口是开放的. dirb没扫出来啥, ...
- 系统模块sys的常用方法
sys.argv参数 sys.argv 等价于是一个列表,argv[0] 代表当前脚本程序路径名,可以通过在外部获取多个参数传递给脚本程序执行,按照列表的取值方式第二个数据就是sys.argv[1] ...
- 2019.7.12 sdfzoier做题统计
lixf_lixf :9 P1981 表达式求值 P1076 寻宝 P1199 三国游戏 P1308 统计单词数 P1190 接水问题 P1158 导弹拦截 P1070 道路游戏 P1069 细胞分裂 ...
- Actor:人生如戏全靠演技--“三维度”逻辑编程语言的设计(3)
在上一篇介绍了逻辑编程的作用,介绍了逻辑编程中的一些概念,包括逻辑程序的结构:事实.规则和问题:知识的表达方式:谓词演算.产生式规则,以及这些概念与三维度(角色+场景+时间)理论的契合关系,正式提出了 ...
- Arduino 串口的一些高级用法
来源: 1.配置串口通信数据位.校验位.停止位 通常我们使用Serial.begin(speed)来完成串口的初始化,这种方式,只能配置串口的波特率. 而使用Serial.begin(speed, c ...
- git仓库之gitlab搭建使用
一.简介 GitLab 是一个用于仓库管理系统的开源项目,使用git作为代码管理工具,并在此基础上搭建起来的web服务.类似github,常用在企业内部做git私有仓库使用: 二.gitlab安装 系 ...
- 洛谷 P1903 [国家集训队]数颜色 / 维护队列 带修莫队
题目描述 墨墨购买了一套\(N\)支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: \(1\). \(Q\) \(L\) \(R\)代表询问你从第\(L\) ...