详解AbstractRoutingDataSource(动态数据源切换)实现原理
当一个项目中有多个数据源(也可以是主从库)的时候,我们可以利用注解在mapper接口上标注数据源,从而来实现多个数据源在运行时的动态切换。
看下AbstractRoutingDataSource:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
AbstractRoutingDataSource继承了AbstractDataSource,获取数据源部分:
抽象方法determineCurrentLookupKey()返回DataSource的key值,然后根据这个key从resolvedDataSources这个map里取出对应的DataSource,如果找不到,则用默认的resolvedDefaultDataSource。
我们要做的就是实现抽象方法determineCurrentLookupKey()返回数据源的key值。
自定义注解:
/**
- 该注解注释在service方法上,标注为链接master库
- Created on 2017/10/24.
- @author guohuan
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProductMaster {
}
/**
- 该注解注释在service方法上,标注为链接slaves库
- Created on 2017/10/24.
- @author guohuan
*/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ProductSlave {
}
AOP实现自定义注解:
package com.jiuyi.ndr.datasource;
import com.jiuyi.ndr.domain.config.DbContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/**
- 读写分离AOP
- @author guohuan
- create on 2017/10/24.
*/
@Aspect
@Component
public class MasterSlaveAspect implements Ordered {
public static final Logger logger = LoggerFactory.getLogger(MasterSlaveAspect.class);
/**
* 切换到product主库
*
* @param proceedingJoinPoint
* @param productMaster
* @return
* @throws Throwable
*/
@Around("@annotation(productMaster)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ProductMaster productMaster) throws Throwable {
try {
logger.info("set database connection to product-master only");
DbContextHolder.setDbType(DbContextHolder.DbType.MASTER);
Object result = proceedingJoinPoint.proceed();
return result;
} finally {
DbContextHolder.clearDbType();
logger.info("restore database connection");
}
}
/**
* 切换到product从库
*
* @param proceedingJoinPoint
* @param productSlave
* @return
* @throws Throwable
*/
@Around("@annotation(productSlave)")
public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ProductSlave productSlave) throws Throwable {
try {
logger.info("set database connection to product-slave only");
DbContextHolder.setDbType(DbContextHolder.DbType.SLAVE);
Object result = proceedingJoinPoint.proceed();
return result;
} finally {
DbContextHolder.clearDbType();
logger.info("restore database connection");
}
}
@Override
public int getOrder() {
return 0;
}
}
加载数据源
DataSourceConfiguration
@Configuration
@EnableTransactionManagement
public class DataSourceConfiguration {
@Value("${spring.type}")
private Class<? extends DataSource> dataSourceType;
@Bean(name = "dataSourceRW")
@ConfigurationProperties(prefix = "spring.datasourceRW")
public DataSource dataSourceRW() {
System.out.println("dataSourceRW init");
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name = "dataSourceR")
@ConfigurationProperties(prefix = "spring.datasourceR")
public DataSource dataSourceR() {
System.out.println("dataSourceR init");
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name = "dataSource")
@Primary
public AbstractRoutingDataSource dataSource() {
System.out.println("dataSource init");
ReadWriteSplitRoutingDataSource proxy = new ReadWriteSplitRoutingDataSource();
Map<Object, Object> targetDataResources = new HashMap<>(2);
targetDataResources.put(DbContextHolder.DbType.MASTER, dataSourceRW());
targetDataResources.put(DbContextHolder.DbType.SLAVE, dataSourceR());
proxy.setDefaultTargetDataSource(dataSourceRW());
proxy.setTargetDataSources(targetDataResources);
proxy.afterPropertiesSet();
return proxy;
}