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默认接管了所有的数据源, 如果我们有多个非分表的库时,则最多只能设置一个为默认数据库, 其他的非分表数据库不能访问 ...
随机推荐
- 微服务实战系列(四)-注册中心springcloud alibaba nacos
1.场景描述 因要用到微服务,关于注册中心这块,与同事在技术原型上做了讨论,初步定的方案是使用:阿里巴巴的nacos+springcloud gateway,下面表格是同事整理的注册中心对比,以前用的 ...
- spring整合(Junit、web)
1.整合Junit (1)整合前的测试类代码 public class Test { public static void main(String[] args) { ApplicationConte ...
- Python-用装饰器实现递归剪枝
求一个共有10个台阶的楼梯,从下走到上面,一次只能迈出1~3个台阶,并且不能后退,有多少中方法? 上台阶问题逻辑整理: 每次迈出都是 1~3 个台阶,剩下就是 7~9 个台阶 如果迈出1个台阶,需要求 ...
- Python练习题 006:输出九九乘法表
[Python练习题 006] 输出九九乘法表 --------------------------------------------------- 照理这题不难,逻辑关系弄对了就好办,但数学渣的我 ...
- 015 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 09 Unicode编码
015 01 Android 零基础入门 01 Java基础语法 02 Java常量与变量 09 Unicode编码 本文知识点:Unicode编码以及字符如何表示? ASCII码是美国提出的标准信息 ...
- 【奇淫巧技】sqlmap绕过过滤的tamper脚本分类汇总
sqlmap绕过过滤的tamper脚本分类汇总
- Open CV leaning
刚接触Open CV 几个比较好的介绍: OpenCV学习笔记:https://blog.csdn.net/yang_xian521/column/info/opencv-manual/3 OpenC ...
- matlab中set用法
来源:https://www.cnblogs.com/sddai/p/5467500.html 1.MATLAB给每种对象的每一个属性规定了一个名字,称为属性名,而属性名的取值成为属性值.例如,Lin ...
- 风车签名 - 让管理APP变成一件简单的事儿
这是一款在Mac平台下安全可控的iOS签名管理软件,旨在对签名后的APP能够完全控制,包括APP的开启或禁用.设置到期时间锁.注入第三方动态库文件.设置安装限量.修改APP名称和自定义Bundle I ...
- 2017年 实验四 B2C模拟实验
实验四 B2C模拟实验 [实验目的] 掌握网上购物的基本流程和B2C平台的运营 [实验条件] ⑴.个人计算机一台 ⑵.计算机通过局域网形式接入互联网. (3).奥派电子商 ...