在項目中在某些時候可能會需要同時連接使用多個不同的數據庫,這就是我們今天要說的多數據源問題??梢允侵鲝牡膱鼍埃鲙靾?zhí)行增刪改的業(yè)務邏輯,從庫進行大量復雜查詢、報表之類的,從而不影響主要業(yè)務。也可以是業(yè)務邏輯設計到多個主數據庫的問題,我這里是不太推薦的,如果可以的話最好拆分成微服務進行調用。多個數據源中處理起事務也是非常麻煩的,而且也沒有必要的,相比之下使用一些補償機制達到最終用一致性也許是更好的選擇。這個也沒有絕對性的,因需求而異。
這個實現方式網上早已有許多成熟的例子,我也是借鑒了一些,記錄一下,讓自己熟悉熟悉。如果涉及到一些版權問題。請及時聯系,非常抱歉。
主要實現方式是注解+AOP
下面總結下步驟:
- 創(chuàng)建一個動態(tài)數據源的對象
/**
* Created by baixiangzhu on 2017/4/19.
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
- 注冊數據源。通過spring的Environment對象,讀取配置文件中配置的多個數據源配置。創(chuàng)建動態(tài)數據源對象,注冊到spring容器中。
/**
* Created by baixiangzhu on 2017/4/19.
*/
@Slf4j
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar,EnvironmentAware{
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 如配置文件中未指定數據源類型,使用該默認值
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
// 數據源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = Maps.newHashMap();
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = Maps.newHashMap();
// 將主數據源添加到更多數據源中
targetDataSources.put("dataSource", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
// 添加更多數據源
targetDataSources.putAll(customDataSources);
//記錄注冊的數據源key
customDataSources.keySet().stream().forEach( e -> DynamicDataSourceContextHolder.dataSourceIds.add(e));
// 創(chuàng)建DynamicDataSource
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);
log.info("Dynamic DataSource Registry");
}
@SuppressWarnings("unchecked")
public DataSource buildDataSource(Map<String, Object> dsMap) {
try {
Object type = dsMap.get("type");
if (type == null)
type = DATASOURCE_TYPE_DEFAULT;// 默認DataSource
Class<? extends DataSource> dataSourceType;
dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);
String driverClassName = dsMap.get("driver-class-name").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);
return factory.build();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 加載多數據源配置
*/
@Override
public void setEnvironment(Environment env) {
initDataSource(env);
}
/**
* 初始化主數據源
*/
private void initDataSource(Environment env) {
// 讀取主數據源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
//獲取所有數據源名稱
String[] dataSourceNames = dsPrefixs.split(",");
//綁定主數據源
Arrays.stream(dataSourceNames).filter( e -> isMaster(e)).forEach( e-> bindMasterDataSource(env));
//綁定從數據源
Arrays.stream(dataSourceNames).filter( e -> !isMaster(e)).forEach(e-> bindSlaveDataSource(e,env));
}
/**
* 綁定數據源
* @param dsPrefix
* @param env
*/
private void bindSlaveDataSource(String dsPrefix, Environment env) {
RelaxedPropertyResolver otherPropertyResolver = new RelaxedPropertyResolver(env, dsPrefix + ".datasource.");
Map<String, Object> dsMap=this.convertData(otherPropertyResolver);
DataSource ds = buildDataSource(dsMap);
dataBinder(ds, env);
customDataSources.put(dsPrefix, ds);
}
/**
* 綁定默認數據源
*/
private void bindMasterDataSource(Environment env) {
//綁定默認數據源
RelaxedPropertyResolver defaulPropertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");
Map<String, Object> dsMap=this.convertData(defaulPropertyResolver);
defaultDataSource = buildDataSource(dsMap);
dataBinder(defaultDataSource, env);
}
private boolean isMaster(String dataSourceName) {
return "master".equals(dataSourceName);
}
private Map<String,Object> convertData(RelaxedPropertyResolver pr){
Map<String, Object> dsMap = Maps.newHashMap();
dsMap.put("type", pr.getProperty("type"));
dsMap.put("driver-class-name", pr.getProperty("driver-class-name"));
dsMap.put("url", pr.getProperty("url"));
dsMap.put("username", pr.getProperty("username"));
dsMap.put("password", pr.getProperty("password"));
return dsMap;
}
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, "offlinetrade_master.datasource").getSubProperties(".");
Map<String, Object> values =Maps.newHashMap(rpr);
// 排除已經設置的屬性
values.remove("type");
values.remove("driver-class-name");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
}
注:我這里的數據庫配置,主數據源(即默認數據源)在config配置中心的application.properties文件中,從數據源在本地的bootstrap.yml 文件中,從數據源可以有多個,我這里只配置了一個。
數據源配置如下:
-
主數據源:
Paste_Image.png 從數據源

Paste_Image.png
3.創(chuàng)建一個注解,主要作用的標記dao使用的數據源??梢杂迷诜椒ㄉ虾皖惿?,但是不能用在mapper的接口上。
/**
* Created by baixiangzhu on 2017/4/19.
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UseDataSource {
String value() default "";
}
4.創(chuàng)建一個維護數據源的容器,主要是記錄數據源的名字。就是注解中需要配置的名稱.
/**
* Created by baixiangzhu on 2017/4/19.
*/
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static List<String> dataSourceIds = Lists.newArrayList();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
public static boolean containsDataSource(String dataSourceId){
return dataSourceIds.contains(dataSourceId);
}
}
5.寫一個AOP類,去攔截標有數據源的方法或者類,根據注解的數據源動態(tài)替換
/**
* Created by baixiangzhu on 2017/4/19.
*/
@Slf4j
@Aspect
@Order(-1)// 保證該AOP在@Transactional之前執(zhí)行
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(ds)")
public void changeDataSource(JoinPoint point, UseDataSource ds) throws Throwable {
String dsId = ds.value();
if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
log.error("數據源[{}]不存在,使用默認數據源 > {}", ds.value(), point.getSignature());
} else {
log.debug("Use DataSource : {} > {}", ds.value(), point.getSignature());
DynamicDataSourceContextHolder.setDataSourceType(ds.value());
}
}
@After("@annotation(ds)")
public void restoreDataSource(JoinPoint point, UseDataSource ds) {
log.debug("Revert DataSource : {} > {}", ds.value(), point.getSignature());
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
至此,重要的實現已經完成。現在來看看如何使用:
- 需要在項目的啟動類中引入動態(tài)注入數據源的類

Paste_Image.png
- 在需要使用數據的方法或者類,添加數據源注解

Paste_Image.png
這下小伙伴們就可以愉快的使用了哈。如果有任何疑問,歡迎留言
