在实际业务场景中,数据量迅速增长,一个库一个表已经满足不了我们的需求的时候,我们就会考虑多数据库多表操作;在springboot项目中使用AOP实现多数据源动态数据源切换.
1.创建动态数据源适配器,使用threadLocal实现多数据源相互不会串扰
public class DynamicDataSourceContextHolder {
private static final ThreadLocal contextHolder = new ThreadLocal();
public static List dataSourceIds = new ArrayList();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
/**
* 判断指定DataSrouce当前是否存在
*
* @param dataSourceId
* @return
*/
public static boolean containsDataSource(String dataSourceId) {
return dataSourceIds.contains(dataSourceId);
}
}
2.创建动态路由,继承AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
3.创建多数据源切换的切面
@Aspect
@Order(-10)
@Component
public class DynamicDataSourceAspect {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
/**
* 拦截注解并切换数据源
*
* @param point
* @param targetDataSource
* @throws Throwable
*/
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Throwable {
// 注解获得对应的数据的标识id
String dataSourceId = targetDataSource.value();
// 到适配器中查找对应的数据源
if (!DynamicDataSourceContextHolder.containsDataSource(dataSourceId)) {
log.info("数据源[{}]不存在,使用默认数据源 > {}" + targetDataSource.value() + point.getSignature());
} else {
log.info("UseDataSource : {} > {}" + targetDataSource.value() + point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
}
}
/**
* 重置数据源
*
* @param point
* @param targetDataSource
*/
@After("@annotation(targetDataSource)")
public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
log.info("RevertDataSource : {} > {}" + targetDataSource.value() + point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
- 多数据源注册器
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
/**
* 默认数据源类型
*/
private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
/**
* Class类型转换
*/
private ConversionService conversionService = new DefaultConversionService();
/**
* 数据源属性值
*/
private PropertyValues dataSourcePropertyValues;
/**
* 默认的数据源
*/
private DataSource defaultDataSource;
/**
* 多数据
*/
private Map customDataSources = new HashMap();
@Override
public void setEnvironment(Environment environment) {
log.info("DynamicDataSourceRegister.setEnvironment() start !!!");
// 初始化默认数据源
initDefaultDataSource(environment);
// 初始化其他数据源
initCustomDataSources(environment);
}
private void initDefaultDataSource(Environment env) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map dsMap = new HashMap();
dsMap.put("type", propertyResolver.getProperty("type"));
dsMap.put("driverClassName", propertyResolver.getProperty("driverClassName"));
dsMap.put("url", propertyResolver.getProperty("url"));
dsMap.put("username", propertyResolver.getProperty("username"));
dsMap.put("password", propertyResolver.getProperty("password"));
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
}
private void initCustomDataSources(Environment env) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");
//获得多个数据源的前缀
String dataSourcePrefixs = propertyResolver.getProperty("names");
// 切割获得每个数据源的前缀
String[] prefixs = dataSourcePrefixs.split(",");
for (String dsPrefix : prefixs) {
Map dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
}
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map dsMap) {
Object type = dsMap.get("type");
if (type == null) {
type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
}
DataSource dataSource = null;
try {
Class extends DataSource> dataSourceType = (Class extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driverClassName").toString();
String url = dsMap.get("url").toString();
String username = dsMap.get("username").toString();
String password = dsMap.get("password").toString();
DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)
.username(username).password(password).type(dataSourceType);
dataSource = factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return dataSource;
}
private void dataBinder(DataSource dataSource, Environment env) {
RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);
dataBinder.setConversionService(conversionService);
dataBinder.setIgnoreNestedProperties(false);// false
dataBinder.setIgnoreInvalidFields(false);// false
dataBinder.setIgnoreUnknownFields(true);// true
if (dataSourcePropertyValues == null) {
Map rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map values = new HashMap<>(rpr);
values.remove("type");
values.remove("driverClassName");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
log.info("DynamicDataSourceRegister.registerBeanDefinitions() start !!!");
Map
5.定义切换数据源的注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
- 在service指定对应的数据,打上对应@TargetDataSource
@TargetDataSource(value = "db1")
@Override
public List
7.在application.properties中配置多数据源
#多数据源配置
# 可以配多个数据源,之间用,隔开
custom.datasource.names=db1,db2,db3......
#DB1
custom.datasource.db1.url=jdbc:oracle:thin:@(description=(address=(protocol=tcp)(port=1521)(host=127.0.0.1))(connect_data=(service_name=db1)))
custom.datasource.db1.username=user_name
custom.datasource.db1.password=pass_word
custom.datasource.db1.driverClassName=oracle.jdbc.driver.OracleDriver
custom.datasource.db1.type=com.alibaba.druid.pool.DruidDataSource
#DB2
custom.datasource.db2.url=jdbc:oracle:thin:@(description=(address=(protocol=tcp)(port=1521)(host=127.0.0.1))(connect_data=(service_name=db2)))
custom.datasource.db2.username=user_name
custom.datasource.db2.password=pass_word
custom.datasource.db2.driverClassName=oracle.jdbc.driver.OracleDriver
custom.datasource.db2.type=com.alibaba.druid.pool.DruidDataSource
#DB3
custom.datasource.db3.url=jdbc:oracle:thin:@(description=(address=(protocol=tcp)(port=1521)(host=127.0.0.1))(connect_data=(service_name=db3)))
custom.datasource.db3.username=user_name
custom.datasource.db3.password=pass_word
custom.datasource.db3.driverClassName=oracle.jdbc.driver.OracleDriver
custom.datasource.db3.type=com.alibaba.druid.pool.DruidDataSource