在實際業(yè)務場景中逆趋,數(shù)據(jù)量迅速增長,一個庫一個表已經(jīng)滿足不了我們的需求的時候晒奕,我們就會考慮多數(shù)據(jù)庫多表操作;在springboot項目中使用AOP實現(xiàn)多數(shù)據(jù)源動態(tài)數(shù)據(jù)源切換.
1.創(chuàng)建動態(tài)數(shù)據(jù)源適配器,使用threadLocal實現(xiàn)多數(shù)據(jù)源相互不會串擾
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static List<String> dataSourceIds = new ArrayList<String>();
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.創(chuàng)建動態(tài)路由,繼承AbstractRoutingDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
3.創(chuàng)建多數(shù)據(jù)源切換的切面
@Aspect
@Order(-10)
@Component
public class DynamicDataSourceAspect {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
/**
* 攔截注解并切換數(shù)據(jù)源
*
* @param point
* @param targetDataSource
* @throws Throwable
*/
@Before("@annotation(targetDataSource)")
public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource) throws Throwable {
// 注解獲得對應的數(shù)據(jù)的標識id
String dataSourceId = targetDataSource.value();
// 到適配器中查找對應的數(shù)據(jù)源
if (!DynamicDataSourceContextHolder.containsDataSource(dataSourceId)) {
log.info("數(shù)據(jù)源[{}]不存在闻书,使用默認數(shù)據(jù)源 > {}" + targetDataSource.value() + point.getSignature());
} else {
log.info("UseDataSource : {} > {}" + targetDataSource.value() + point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.value());
}
}
/**
* 重置數(shù)據(jù)源
*
* @param point
* @param targetDataSource
*/
@After("@annotation(targetDataSource)")
public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource) {
log.info("RevertDataSource : {} > {}" + targetDataSource.value() + point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
- 多數(shù)據(jù)源注冊器
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
/**
* 默認數(shù)據(jù)源類型
*/
private static final Object DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";
/**
* Class類型轉(zhuǎn)換
*/
private ConversionService conversionService = new DefaultConversionService();
/**
* 數(shù)據(jù)源屬性值
*/
private PropertyValues dataSourcePropertyValues;
/**
* 默認的數(shù)據(jù)源
*/
private DataSource defaultDataSource;
/**
* 多數(shù)據(jù)
*/
private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
@Override
public void setEnvironment(Environment environment) {
log.info("DynamicDataSourceRegister.setEnvironment() start !!!");
// 初始化默認數(shù)據(jù)源
initDefaultDataSource(environment);
// 初始化其他數(shù)據(jù)源
initCustomDataSources(environment);
}
private void initDefaultDataSource(Environment env) {
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map<String, Object> dsMap = new HashMap<String, Object>();
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.");
//獲得多個數(shù)據(jù)源的前綴
String dataSourcePrefixs = propertyResolver.getProperty("names");
// 切割獲得每個數(shù)據(jù)源的前綴
String[] prefixs = dataSourcePrefixs.split(",");
for (String dsPrefix : prefixs) {
Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + ".");
DataSource ds = buildDataSource(dsMap);
customDataSources.put(dsPrefix, ds);
dataBinder(ds, env);
}
}
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String, Object> 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<String, Object> rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");
Map<String, Object> 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<Object, Object> targetDataSources = new HashMap<Object, Object>();
targetDataSources.put("dataSource", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
targetDataSources.putAll(customDataSources);
for (String key : customDataSources.keySet()) {
DynamicDataSourceContextHolder.dataSourceIds.add(key);
}
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
mpv.addPropertyValue("targetDataSources", targetDataSources);
registry.registerBeanDefinition("dataSource", beanDefinition);
}
}
5.定義切換數(shù)據(jù)源的注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value();
}
- 在service指定對應的數(shù)據(jù),打上對應@TargetDataSource
@TargetDataSource(value = "db1")
@Override
public List<Map<String, Object>> querySyncData(String tableName) {
List<Map<String, Object>> syncDataList = syncDataBaseDao.querySyncData(tableName);
return syncDataList;
}
7.在application.properties中配置多數(shù)據(jù)源
#多數(shù)據(jù)源配置
# 可以配多個數(shù)據(jù)源,之間用,隔開
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