⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 blog.csdn.net/weixin_45840947/article/details/123892165 「kenewstar」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

本案例我们使用多数据源封装成一个starter组件,以方便使用多数据源访问数据库的操作

创建一个普通Java项目,引入SpringBoot相关的依赖

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/>
</parent>
<groupId>com.gitee.kenewstar.multi.datasource</groupId>
<artifactId>multi-datasource-spring-boot-starter</artifactId>
<version>0.0.1</version>
<name>multi-datasource-spring-boot-starter</name>
<description>multi-datasource-spring-boot-starter</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>

</project>

创建常量类和注解

public interface Const {

String DEFAULT = "default";

String MULTI_DS = "multiDataSource";

String CONFIG_PREFIX = "spring.datasource.multi";

}

数据源注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {

String value() default Const.DEFAULT;

}

创建多数据源属性类

主要用于存储SpringBoot配置文件中配置的数据源属性

@Component
@ConfigurationProperties(prefix = Const.CONFIG_PREFIX)
public class MultiDataSourceProperties {

private Map<String, DataSourceProp> dataSourcePropMap;

public Map<String, DataSourceProp> getDataSourcePropMap() {
return dataSourcePropMap;
}

public void setDataSourcePropMap(Map<String, DataSourceProp> dataSourcePropMap) {
this.dataSourcePropMap = dataSourcePropMap;
}
}


public class DataSourceProp extends HashMap<String, String> {


}

创建数据源key的切换工具

主要用于设置当前线程下数据源切换时的数据源唯一标识key,以便获取指定的数据源

public class DynamicDataSourceHolder {

private static final ThreadLocal<String> DATA_SOURCE_THEAD_LOCAL =
ThreadLocal.withInitial(() -> Const.DEFAULT);


public static String getDataSource() {
return DATA_SOURCE_THEAD_LOCAL.get();
}

public static void setDataSource(String dataSource) {
DATA_SOURCE_THEAD_LOCAL.set(dataSource);
}

public static void remove() {
DATA_SOURCE_THEAD_LOCAL.remove();
}

}

创建多数据源类

创建多数据源类继承AbstractRoutingDataSource类,重写determineCurrentLookupKey()方法,用于获取当前线程中的指定的数据源key,通过该key拿到对应的数据源对象

public class MultiDataSource extends AbstractRoutingDataSource {

private static final Logger LOGGER = Logger.getLogger(MultiDataSource.class.getName());

@Override
protected Object determineCurrentLookupKey() {
String key = DynamicDataSourceHolder.getDataSource();
LOGGER.info("DataSource key ---> " + key);
return key;
}

}

创建多数据源的切面类

切面类主要用于获取被数据与注解指定的方法,拿到其注解中的属性值,再设置到数据源key设置组件中,方便数据源类获取该key

需使用@Order设置切面优先级,否则设置无效

@Aspect
@Order(100)
public class DynamicDataSourceAdviser {

@Pointcut("@annotation(com.gitee.kenewstar.multi.datasource.common.DataSource)")
public void pointcut(){};

@Around("pointcut()")
public Object around(ProceedingJoinPoint point) throws Throwable {

try {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
//获取被代理的方法对象
Method targetMethod = methodSignature.getMethod();
// 获取数据源注解
DataSource ds = targetMethod.getAnnotation(DataSource.class);
if (Objects.nonNull(ds)) {
DynamicDataSourceHolder.setDataSource(ds.value());
}
return point.proceed();
} finally {
DynamicDataSourceHolder.remove();
}


}

}

创建数据源配置类

@Configuration
@EnableConfigurationProperties(MultiDataSourceProperties.class)
public class MultiDataSourceConfig {

public static final String DS_TYPE = "dsType";

@Resource
private MultiDataSourceProperties multiDataSourceProperties;

private DataSource createDs(DataSourceProp dsProp) {
DataSource dataSource = null;
try {
Class<?> dsClass = Class.forName(dsProp.get(DS_TYPE));
if (DataSource.class.isAssignableFrom(dsClass)) {
dataSource = (DataSource) dsClass.getConstructor().newInstance();

DataSource finalDataSource = dataSource;
ReflectionUtils.doWithFields(dsClass, field -> {
field.setAccessible(true);
field.set(finalDataSource, dsProp.get(field.getName()));
}, field -> {
if (Objects.equals(dsProp.get(DS_TYPE), field.getName())) {
return false;
}
return Objects.nonNull(dsProp.get(field.getName()));
});

}

} catch (Exception e) {
throw new RuntimeException(e);
}
return dataSource;
}

@Bean(Const.MULTI_DS)
@Primary
public DataSource multiDataSource() {
MultiDataSource multiDataSource = new MultiDataSource();

Map<Object, Object> dataSourceMap = new HashMap<>(multiDataSourceProperties.getDataSourcePropMap().size());
Map<String, DataSourceProp> dataSourcePropMap = multiDataSourceProperties.getDataSourcePropMap();
dataSourcePropMap.forEach((lookupKey,dsProp) -> {
dataSourceMap.put(lookupKey, createDs(dsProp));
});

multiDataSource.setTargetDataSources(dataSourceMap);
multiDataSource.setDefaultTargetDataSource(dataSourceMap.get(Const.DEFAULT));
return multiDataSource;
}

@Bean
public DataSourceTransactionManager dataSourceTransactionManager(
@Qualifier(Const.MULTI_DS) DataSource multiDataSource) {

DataSourceTransactionManager tx = new DataSourceTransactionManager();
tx.setDataSource(multiDataSource);
return tx;
}

@Bean
public DynamicDataSourceAdviser dynamicDataSourceAdviser() {
return new DynamicDataSourceAdviser();
}

}

配置spring.factories文件

在resources目录下创建META-INF目录,在该目录创建spring.factories

文件内容如下:

设置key为开启自动配置的注解全路径名,后面的value值为配置类全路径名,本starter组件中为数据源配置类,如有多个配置类,则以逗号分隔,以反斜杆表示忽略换行

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.gitee.kenewstar.multi.datasource.config.MultiDataSourceConfig

这样我们封装的一个简单的多数据源starter组件就完成了,只需进行maven打包即可在本地使用

maven命令:mvn clean install

使用示例

引入打包后的依赖

<dependency>
<groupId>com.gitee.kenewstar.multi.datasource</groupId>
<artifactId>multi-datasource-spring-boot-starter</artifactId>
<version>0.0.1</version>
</dependency>

修改SpringBoot全局配置文件

default为默认数据源,必须配置, master为可选数据源,名称可自定义。

数据源的属性名称为对应的dsType数据源类型的属性字段

spring:
datasource:
multi:
data-source-prop-map:
default:
dsType: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3306/test
username: root
password: kenewstar
master:
dsType: com.zaxxer.hikari.HikariDataSource
jdbcUrl: jdbc:mysql://localhost:3306/test2
username: root
password: kenewstar

使用数据源

直接在指定的方法上添加@DataSource注解即可,注解的默认值为default,数据源的切换通过注解的值进行切换。值为application.yml中配置的default,master等

@Service
public class PersonService {

@Resource
private PersonMapper personMapper;

@DataSource("master")
@Transactional(rollbackFor = Exception.class)
public void insertPerson() {
personMapper.insert(new Person(null, "kk", 12));
personMapper.insert(new Person(null, "kk", 12));
}

}
@Service
public class PersonService {

@Resource
private PersonMapper personMapper;

@DataSource("master")
@Transactional(rollbackFor = Exception.class)
public void insertPerson() {
personMapper.insert(new Person(null, "kk", 12));
personMapper.insert(new Person(null, "kk", 12));
}
}

多数据源starter组件源码地址:

https://gitee.com/kenewstar/multi-datasource-spring-boot-starter

文章目录
  1. 1. 创建常量类和注解
  2. 2. 创建多数据源属性类
  3. 3. 创建数据源key的切换工具
  4. 4. 创建多数据源类
  5. 5. 创建多数据源的切面类
  6. 6. 创建数据源配置类
  7. 7. 配置spring.factories文件
  8. 8. 使用示例