以下内容转载自:https://my.oschina.net/u/3681868/blog/1813011
一.引入Jar包:
<dependency> <!-- MySql驱动 -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency> <!-- 连接池 -->
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
二,.配置参数:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.jdbc.Driver
druid:
one: #数据源1
url: jdbc:mysql://localhost:3306/test1?...
username: root
password: root
two: #数据源2
url: jdbc:mysql://localhost:3306/test2?...
username: root
password: root
initial-size: 10
max-active: 100
min-idle: 10
max-wait: 60000
pool-prepared-statements: true
max-pool-prepared-statement-per-connection-size: 20
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
stat-view-servlet:
enabled: true
url-pattern: /druid/*
#login-username: admin
#login-password: admin
filter:
stat:
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
(参数配置,可参考: https://gitee.com/wenshao/druid/tree/master/druid-spring-boot-starter)
三. 编写配置文件:
1. 定义数据源名称常量 :
package com.gy.fast.common.config.data;
/**
* 数据源名称
* @author geYang
* @date 2018-05-14
*/
public interface DataSourceNames {
String ONE = "ONE";
String TWO = "TWO";
}
2. 创建动态数据源:
package com.gy.fast.common.config.data;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
/**
* 动态数据源
* @author geYang
* @date 2018-05-14
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
/**
* 配置DataSource, defaultTargetDataSource为主数据库
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
}
}
3. 动态数据源配置:
package com.gy.fast.common.config.data;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 配置多数据源
* @author geYang
* @date 2018-05-14
*/
@Configuration
public class DynamicDataSourceConfig {
/**
* 创建 DataSource Bean
* */
@Bean
@ConfigurationProperties("spring.datasource.druid.one")
public DataSource oneDataSource(){
DataSource dataSource = DruidDataSourceBuilder.create().build();
return dataSource;
}
@Bean
@ConfigurationProperties("spring.datasource.druid.two")
public DataSource twoDataSource(){
DataSource dataSource = DruidDataSourceBuilder.create().build();
return dataSource;
}
/**
* 如果还有数据源,在这继续添加 DataSource Bean
* */
@Bean
@Primary
public DynamicDataSource dataSource(DataSource oneDataSource, DataSource twoDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DataSourceNames.ONE, oneDataSource);
targetDataSources.put(DataSourceNames.TWO, twoDataSource);
// 还有数据源,在targetDataSources中继续添加
System.out.println("DataSources:" + targetDataSources);
return new DynamicDataSource(oneDataSource, targetDataSources);
}
}
4. 定义动态数据源注解:
package com.gy.fast.common.config.data;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 多数据源注解
* @author geYang
* @date 2018-05-14
*/
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default DataSourceNames.ONE;
}
5. 设置数据源 AOP 代理:
package com.gy.fast.common.config.data;
import java.lang.reflect.Method;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/**
* 数据源AOP切面处理
* @author geYang
* @date 2018-05-14
*/
@Aspect
@Component
public class DataSourceAspect implements Ordered {
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* 切点: 所有配置 DataSource 注解的方法
*/
@Pointcut("@annotation(com.gy.fast.common.config.data.DataSource)")
public void dataSourcePointCut() {}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource ds = method.getAnnotation(DataSource.class);
// 通过判断 DataSource 中的值来判断当前方法应用哪个数据源
DynamicDataSource.setDataSource(ds.value());
System.out.println("当前数据源: " + ds.value());
logger.debug("set datasource is " + ds.value());
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
logger.debug("clean datasource");
}
}
@Override
public int getOrder() {
return 1;
}
}
四. 修改启动文件:
package com.gy.fast;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Import;
import com.gy.fast.common.config.data.DynamicDataSourceConfig;
/**
* 动态数据源配置,需要将自有的配置依赖(DynamicDataSourceConfig),将原有的依赖去除(DataSourceAutoConfiguration)
* @author geYang
* @date 2018-05-15
*/
@Import({DynamicDataSourceConfig.class})
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
public class FastApplication {
public static void main(String[] args) {
SpringApplication.run(FastApplication.class, args);
}
}
五. 配置完成, 进行测试:
package com.gy.fast;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.gy.fast.common.config.data.DataSource;
import com.gy.fast.common.config.data.DataSourceNames;
import com.gy.fast.module.sys.entity.SysUser;
import com.gy.fast.module.sys.service.SysUserService;
/**
* 测试多数据源
* @author geYang
* @date 2018-05-15
*/
@Service
public class DataSourceTestService {
@Autowired
private SysUserService sysUserService;
public SysUser test1(Long userId){
return sysUserService.selectById(userId);
}
@DataSource(DataSourceNames.TWO)
public SysUser test2(Long userId){
return sysUserService.selectById(userId);
}
}
package com.gy.fast;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.gy.fast.module.sys.entity.SysUser;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DynamicDataSourceTest {
@Autowired
private DataSourceTestService dataSourceTestService;
@Test
public void test(){
// 数据源ONE
SysUser user1 = dataSourceTestService.test1(1L);
System.out.println(ToStringBuilder.reflectionToString(user1));
// 数据源TWO
SysUser user2 = dataSourceTestService.test2(1L);
System.out.println(ToStringBuilder.reflectionToString(user2));
// 数据源ONE
SysUser user3 = dataSourceTestService.test1(1L);
System.out.println(ToStringBuilder.reflectionToString(user3));
}
}
具体应用场景
现有5个数据库,需要查询这5个库中的同一个表,已知记录是根据ID通过一定的算法映射到5个库的。
1.根据ID得到记录所在的库索引;
2.根据索引到对应的库查询。
这里有个问题,目前是通过注解@DataSource的形式来确定要使用的数据库,但是注解无法传入变量,因此只能写5个方法来分别查询。这里的结构如下,serviceInterface中定义查询方法,并由baseService实现,service1分别继承该方法(super().XXX),并且上面使用不同数据库的注解,这样既可。