# data-source-demo
**Repository Path**: feizhaiyou/data-source-demo
## Basic Information
- **Project Name**: data-source-demo
- **Description**: 该项目是SpringBoot基于Druid连接池,通过AOP实现多数据源的配置与切换,用于代码中链接不同类型的数据库。
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 1
- **Created**: 2021-12-24
- **Last Updated**: 2022-04-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: druid, SpringBoot, Oracle, MySQL, aop
## README
# 应用场景
单个项目中需要连接多个数据源(同域同类型不同库,同域不同类型,不同域...)
# 核心技术
AOP:通过类、方法切入,在执行前获取所需要的数据源。
AbstractRoutingDataSource:JDBC提供,用来配置多个数据源,可以通过指定key获取配置好的数据源。【DataSource抽象实现类】
数据源配置类:配置多个数据源的bean,存放到IOC容器中,并将所有数据源bean设置到AbstractRoutingDataSource的动态数据源中。
SpringBoot启动类:排除数据源自动装配DataSourceAutoConfiguration。
# 配置解析
## 1.Maven依赖
~~~xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.8.RELEASE
com.sen
data-source-demo
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
2.1.1
org.springframework.boot
spring-boot-starter-jdbc
mysql
mysql-connector-java
8.0.21
com.oracle
ojdbc8
18.3
org.springframework.boot
spring-boot-starter-aop
org.aspectj
aspectjweaver
1.9.1
com.alibaba
druid-spring-boot-starter
1.1.14
~~~
## 2.yml配置文件
application.yml
~~~yml
logging:
level:
com.feizhaiyou.demo: debug
mybatis:
mapper-locations: mapper/*.xml
server:
port: 10010
spring:
profiles:
active: druid
~~~
application-druid.yml
~~~yml
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
master:
# mysql8.0驱动配置
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 从库数据源
slave:
# 是否开启从数据源(如果不是true会使用默认数据源MASTER)
enabled: true
driverClassName: oracle.jdbc.driver.OracleDriver(驱动类)
url: 数据库URL(公司的Orcale数据库不便展示)
username: 自己的数据库连接名
password: 自己的数据库密码
slave2:
enabled: true
driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: 123
login-password: 123
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
~~~
说明:为了方便,单独拉出的yml文件配置数据源
## 3.自定义注解及多数据源Key
注解用来作为AOP的切点
```java
package com.feizhaiyou.demo.anno;
import com.feizhaiyou.demo.enums.DataSourceType;
import java.lang.annotation.*;
/**
* 自定义多数据源切换注解
*
* @author Jason
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource {
/**
* 切换数据源key 默认master
*/
public DataSourceType value() default DataSourceType.MASTER;
}
```
枚举类用来作为数据源的key
~~~java
package com.feizhaiyou.demo.enums;
/**
* 数据源key
*
* @author Jason
*/
public enum DataSourceType {
/**
* 主库
*/
MASTER,
/**
* 从库
*/
SLAVE,
/**
* 从库2
*/
SLAVE2,
}
~~~
## 4.设置AOP切面
用来获取指定的数据源配置
~~~java
package com.feizhaiyou.demo.aop;
import com.feizhaiyou.demo.anno.DataSource;
import com.feizhaiyou.demo.holder.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 多数据源处理
*
* @author Jason
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
// 切点
@Pointcut("@annotation(com.feizhaiyou.demo.anno.DataSource)"
+ "|| @within(com.feizhaiyou.demo.anno.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 获取该切点的DataSource注解
DataSource dataSource = getDataSource(point);
if (dataSource != null) {
// 设置数据源key 通过该key获取DynamicDataSource中指定的动态数据源
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后 清空数据源key 释放该数据源
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取切点上的DataSource注解{@link DataSource}
* @param point 切点
* @return {@link DataSource}
*/
private DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
Class extends Object> targetClass = point.getTarget().getClass();
DataSource targetDataSource = targetClass.getAnnotation(DataSource.class);
if (targetDataSource != null) {
return targetDataSource;
} else {
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
return dataSource;
}
}
}
~~~
## 5.设置线程使用的数据源key
该线程会使用存储的key来调用对应的动态数据源
~~~java
package com.feizhaiyou.demo.holder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据源key处理(存储执行线程所需要的数据源的key)
*
* @author Jason
*/
public class DynamicDataSourceContextHolder {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置线程数据源key
*/
public static void setDataSourceType(String dsType) {
log.info("切换到{}数据源", dsType);
CONTEXT_HOLDER.set(dsType);
}
/**
* 获得线程数据源key
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清空线程数据源key
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
~~~
## 6.动态数据源配置【重点】
实现AbstractRoutingDataSource类的方法,指定默认数据源和动态数据源集合
~~~java
package com.feizhaiyou.demo.config;
import com.feizhaiyou.demo.holder.DynamicDataSourceContextHolder;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* 动态数据源,配置好后可以通过key获取数据源
*
* @author Jason
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
* 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
* @return DataSource
*/
@Override
protected DataSource determineTargetDataSource() {
return super.determineTargetDataSource();
}
/**
* 设置需要获取的数据源的key,通过该key获取数据源,该方法直接返回需要获取的数据源的key即可
* targetDataSources找不到该key,则会使用默认数据源
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
/**
* 设置动态数据源,设置后可以通过determineCurrentLookupKey方法返回的key动态获取targetDataSources中的数据源
* @param defaultTargetDataSource 默认数据源
* @param targetDataSources 动态数据源集合:key={@link com.feizhaiyou.demo.enums.DataSourceType} value={@link DataSource}
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map