# dynamic-datasource-demo
**Repository Path**: Ansoya_admin/dynamic-datasource-demo
## Basic Information
- **Project Name**: dynamic-datasource-demo
- **Description**: AOP方式动态数据源
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 15
- **Created**: 2018-10-09
- **Last Updated**: 2020-12-19
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## spring-boot+aop实现多数据源切换
当对同一个请求的QPS达到一定程度时,系统的响应会出现瓶颈,一般都是在数据库上,这个时候数据库一般会采取各种措施,例如主从服务,分表分库,读写分离,缓存技术等等。一旦这几种出现,我们在技术上也要做相应的变通。大多数情况是从原始的单库单表变成了多库多表
例如:我们有一个表user_info,我们护绿其他字段,里面有两个字段id、remarks,当数据量达到一定程序后,系统做了分表分库(你也可以当成是主从)
假设我们有一个库test和test2
test库中数据如下:
id | remarks|
---|---
1 | test1
2 | test1
test2库中数据如下:
id | remarks|
---|---
1 | test2
2 | test2
最初单库单表时,我们可能直接使用spring-boot自动配置的方式,系统没有任何问题。如果我们现在要从两个表中查询数据原来的自动单数据源的方式就不再适用了,这个时候可能就涉及到了多数据源的程序了。多数据源有多种方式,接下来我们介绍采用spring-boot+AOP方式实现多数据源切换。
首先由于使用了spring-boot,我们还是让程序继承spring-boot-starter-parent,这样可以少管理一些版本。
```
org.springframework.boot
spring-boot-starter-parent
1.5.2.RELEASE
```
其次,我们引入相关依赖
```
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-aop
org.springframework.boot
spring-boot-starter-test
org.projectlombok
lombok
mysql
mysql-connector-java
com.zaxxer
HikariCP
com.github.pagehelper
pagehelper-spring-boot-starter
1.2.0
spring-boot-starter
org.springframework.boot
org.springframework.boot
spring-boot-configuration-processor
true
```
以上依赖,为了引入了spring-web、mybatis、AOP、jdbc、mysql驱动、HikariCP连接池。
接下来我们开始实现动态数据源主要代码功能。
> 1、创建线程共享工具
由于我们的数据源信息要保证在同一线程下切换后不要被其他线程修改,所以我们将数据源信息保存在ThreadLocal共享中。
```
/**
* 动态数据源持有者,负责利用ThreadLocal存取数据源名称
*/
public class DynamicDataSourceHolder {
/**
* 本地线程共享对象
*/
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>();
public static void putDataSource(String name) {
THREAD_LOCAL.set(name);
}
public static String getDataSource() {
return THREAD_LOCAL.get();
}
public static void removeDataSource() {
THREAD_LOCAL.remove();
}
}
```
>2、实现动态数据源AbstractRoutingDataSource
spring为我们提供了AbstractRoutingDataSource,即带路由的数据源。继承后我们需要实现它的determineCurrentLookupKey(),该方法用于自定义实际数据源名称的路由选择方法,由于我们将信息保存到了ThreadLocal中,所以只需要从中拿出来即可。
```
/**
* 动态数据源实现类
*/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource{
//数据源路由,此方用于产生要选取的数据源逻辑名称
@Override
protected Object determineCurrentLookupKey() {
//从共享线程中获取数据源名称
return DynamicDataSourceHolder.getDataSource();
}
}
```
>3、创建数据源切换方法注解
我们切换数据源时,一般都是在调用mapper接口的方法前实现,所以我们定义一个方法注解,当AOP检测到方法上有该注解时,根据注解中value对应的名称进行切换。
```
/**
* 目标数据源注解,注解在方法上指定数据源的名称
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TargetDataSource {
String value();//此处接收的是数据源的名称
}
```
>4、定义处理AOP切面
动态数据源切换是基于AOP的,所以我们需要声明一个AOP切面,并在切面前做数据源切换,切面完成后移除数据源名称。
```
/**
* 数据源AOP切面定义
*/
@Component
@Aspect
@Slf4j
public class DataSourceAspect {
//切换放在mapper接口的方法上,所以这里要配置AOP切面的切入点
@Pointcut("execution( * com.comven.example.mapper.*.*(..))")
public void dataSourcePointCut() {
}
@Before("dataSourcePointCut()")
public void before(JoinPoint joinPoint) {
Object target = joinPoint.getTarget();
String method = joinPoint.getSignature().getName();
Class>[] clazz = target.getClass().getInterfaces();
Class>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
try {
Method m = clazz[0].getMethod(method, parameterTypes);
//如果方法上存在切换数据源的注解,则根据注解内容进行数据源切换
if (m != null && m.isAnnotationPresent(TargetDataSource.class)) {
TargetDataSource data = m.getAnnotation(TargetDataSource.class);
String dataSourceName = data.value();
DynamicDataSourceHolder.putDataSource(dataSourceName);
log.debug("current thread " + Thread.currentThread().getName() + " add " + dataSourceName + " to ThreadLocal");
} else {
log.debug("switch datasource fail,use default");
}
} catch (Exception e) {
log.error("current thread " + Thread.currentThread().getName() + " add data to ThreadLocal error", e);
}
}
//执行完切面后,将线程共享中的数据源名称清空
@After("dataSourcePointCut()")
public void after(JoinPoint joinPoint){
DynamicDataSourceHolder.removeDataSource();
}
}
```
>5、定义多个数据源
之前我们假设中访问两个库两个表,假设test库数据源我们命名为test1,test2库数据源我们命名为test2。
我们先定义一个实际数据源配置类
```
/**
* 实际数据源配置
*/
@Component
@Data
@ConfigurationProperties(prefix = "hikari")
public class DBProperties {
private HikariDataSource test1;
private HikariDataSource test2;
}
```
在application.properties中,我们的配置是这样的
```
#test1数据源配置
hikari.test1.jdbc-url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false
hikari.test1.username=root
hikari.test1.password=123456
hikari.test1.maximum-pool-size=10
#test2数据源配置
hikari.test2.jdbc-url=jdbc:mysql://127.0.0.1:3306/test2?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false
hikari.test2.username=root
hikari.test2.password=123456
hikari.test2.maximum-pool-size=10
```
接下来我们采用@Bean注解完成动态数据源对象的申明
```
/**
* 数据源配置
*/
@Configuration
@EnableScheduling
@Slf4j
public class DataSourceConfig {
@Autowired
private DBProperties properties;
@Bean(name = "dataSource")
public DataSource dataSource() {
//按照目标数据源名称和目标数据源对象的映射存放在Map中
Map