@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSourceTarget {
String value() default DataSourceTarget.PAY;
public static String PAY = "payDataSource";
public static String BUZ = "buzDataSource";
public static String ACCOUNT = "accountDataSource";
}
application.properties配置数据源
dataSource.driverClassName = com.mysql.jdbc.Driver # 初始化连接 dataSource.initialSize = 10 # 最大连接数量 dataSource.maxActive = 100 # 最大空闲连接 dataSource.maxIdle = 10 # 最小空闲连接 dataSource.minIdle = 2 # 连接有效性检查 dataSource.testOnBorrow = false dataSource.testWhileIdle = true dataSource.validationQuery = select 1 #pay数据源--finance_pay_v2 pay.dataSource.url = jdbc:mysql://r1.fdb.local/finance_pay_v2?useUnicode=true&characterEncoding=utf-8 pay.dataSource.username = fin_pay_v2 pay.dataSource.password = ****** #buz数据源--finance_buz_v2 buz.dataSource.url = jdbc:mysql://r1.fdb.local/finance_buz_v2?useUnicode=true&characterEncoding=utf-8 buz.dataSource.username = fin_buz_v2 buz.dataSource.password = ****** #account数据源--finance_account_v2 account.dataSource.url = jdbc:mysql://r1.fdb.local/finance_account_v2?useUnicode=true&characterEncoding=utf-8 account.dataSource.username = fin_account_v2 account.dataSource.password = ******继承AbstractRoutingDataSource
SpringBoot提供AbstractRoutingDataSource类让用户根据自己定义的规则选取当前数据源,实现可动态切换的数据源。它的抽象方法 determineCurrentLookupKey() 决定使用哪个数据源。AbstractRoutingDataSource的getConnection() 方法根据查找 lookup key 键对不同目标数据源的调用,通常是通过(但不一定)某些线程绑定的事物上下文来实现。
public class DynamicDataSource extends AbstractRoutingDataSource
{
@Override
protected Object determineCurrentLookupKey()
{
//DynamicDataSourceHolder有获取和设置当前数据库的方法get & put
return DynamicDataSourceHolder.getDataSource();
}
}
使用当前线程操作数据源对应名称
public class DynamicDataSourceHolder {
public static final ThreadLocal HOLDER = new ThreadLocal();
// 将数据源对应的名称放入当前线程
public static void putDataSource(String name) {
HOLDER.set(name);
}
// 将数据源对应的名称从当前线程取出
public static String getDataSource() {
return HOLDER.get();
}
// 清除
public static void clearCustomerType() {
HOLDER.remove();
}
}
读取数据源配置
实现EnvironmentAware接口,重写setEnvironment方法,在工程启动的时候可以获得application.properties配置文件中配置的属性值
@Component
public class DataBaseConfig implements EnvironmentAware{
private Environment environment;
@Override
public void setEnvironment(Environment environment){
this.environment = environment;
}
private DataSource payDataSource() throws Exception {
Properties p =new Properties();
p.put("driverClassName",environment.getRequiredProperty("dataSource.driverClassName"));
p.put("url",environment.getRequiredProperty("pay.dataSource.url"));
p.put("username",environment.getRequiredProperty("pay.dataSource.username"));
p.put("password",environment.getRequiredProperty("pay.dataSource.password"));
p.put("initialSize",environment.getRequiredProperty("dataSource.initialSize"));
p.put("maxActive",environment.getRequiredProperty("dataSource.maxActive"));
p.put("maxIdle",environment.getRequiredProperty("dataSource.maxIdle"));
p.put("minIdle",environment.getRequiredProperty("dataSource.minIdle"));
p.put("testOnBorrow",environment.getRequiredProperty("dataSource.testOnBorrow"));
p.put("testWhileIdle",environment.getRequiredProperty("dataSource.testWhileIdle"));
p.put("validationQuery",environment.getRequiredProperty("dataSource.validationQuery"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(p);
return dataSource;
}
private DataSource buzDataSource() throws Exception{
Properties p =new Properties();
p.put("driverClassName",environment.getRequiredProperty("dataSource.driverClassName"));
p.put("url",environment.getRequiredProperty("buz.dataSource.url"));
p.put("username",environment.getRequiredProperty("buz.dataSource.username"));
p.put("password",environment.getRequiredProperty("buz.dataSource.password"));
p.put("initialSize",environment.getRequiredProperty("dataSource.initialSize"));
p.put("maxActive",environment.getRequiredProperty("dataSource.maxActive"));
p.put("maxIdle",environment.getRequiredProperty("dataSource.maxIdle"));
p.put("minIdle",environment.getRequiredProperty("dataSource.minIdle"));
p.put("testOnBorrow",environment.getRequiredProperty("dataSource.testOnBorrow"));
p.put("testWhileIdle",environment.getRequiredProperty("dataSource.testWhileIdle"));
p.put("validationQuery",environment.getRequiredProperty("dataSource.validationQuery"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(p);
return dataSource;
}
private DataSource accountDataSource() throws Exception{
Properties p =new Properties();
p.put("driverClassName",environment.getRequiredProperty("dataSource.driverClassName"));
p.put("url",environment.getRequiredProperty("account.dataSource.url"));
p.put("username",environment.getRequiredProperty("account.dataSource.username"));
p.put("password",environment.getRequiredProperty("account.dataSource.password"));
p.put("initialSize",environment.getRequiredProperty("dataSource.initialSize"));
p.put("maxActive",environment.getRequiredProperty("dataSource.maxActive"));
p.put("maxIdle",environment.getRequiredProperty("dataSource.maxIdle"));
p.put("minIdle",environment.getRequiredProperty("dataSource.minIdle"));
p.put("testOnBorrow",environment.getRequiredProperty("dataSource.testOnBorrow"));
p.put("testWhileIdle",environment.getRequiredProperty("dataSource.testWhileIdle"));
p.put("validationQuery",environment.getRequiredProperty("dataSource.validationQuery"));
DataSource dataSource = DruidDataSourceFactory.createDataSource(p);
return dataSource;
}
@Bean(name = "dataSource")
public DynamicDataSource dataSource() throws Exception
{
DataSource pay = payDataSource();
DataSource buz = buzDataSource();
DataSource account = accountDataSource();
Map
切面
@Aspect
@Component
public class DataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);
@Pointcut("execution(* com.*.*.multi.base.*.*.*(..))")
private void aspectPoint() {
// 定义一个切入点
}
@Before("aspectPoint()")
public void doBefore(JoinPoint point) {
Object target = point.getTarget();
String method = point.getSignature().getName();
Class>[] classz = target.getClass().getInterfaces();
Class>[] parameterTypes = ((MethodSignature) point.getSignature())
.getMethod().getParameterTypes();
Method m = null;
try {
m = classz[0].getMethod(method, parameterTypes);
logger.info("当前方法:" + m.getName() + "类名称:" + classz[0].getName());
} catch (Exception e) {
logger.error(e.getMessage());
return;
}
if (m != null && m.isAnnotationPresent(DataSourceTarget.class)) {
DataSourceTarget data = m.getAnnotation(DataSourceTarget.class);// 获取访问mapper中的注释
DynamicDataSourceHolder.putDataSource(data.value());// 获取注释中的value值,确定访问的数据源
logger.info("当前数据源---" + data.value());
} else {
logger.info("当前数据源---主数据源");
}
}
@After("aspectPoint()")
public void doAfter(JoinPoint point) {
// 清除数据源配置
DynamicDataSourceHolder.clearCustomerType();
}
}
总结
总体流程就是,工程启动时,将所有使用的数据源以map形式加载到AbstractRoutingDataSource的子类targetDataSources属性中,在方法执行前,由切面检查当前类或者方法上有无自定义注解,如果有,则将注解对应名设置到当前线程中,由AbstractRoutingDataSource切换当前线程中数据源名称对应数据源。在方法执行后,再将该数据源清除,恢复默认数据源



