在項目中在某些時候可能會需要同時連接使用多個不同的數(shù)據(jù)庫模暗,這就是我們今天要說的多數(shù)據(jù)源問題岸晦∨菲。可以是主從的場景,主庫執(zhí)行增刪改的業(yè)務(wù)邏輯启上,從庫進行大量復(fù)雜查詢邢隧、報表之類的,從而不影響主要業(yè)務(wù)碧绞。也可以是業(yè)務(wù)邏輯設(shè)計到多個主數(shù)據(jù)庫的問題府框,我這里是不太推薦的,如果可以的話最好拆分成微服務(wù)進行調(diào)用讥邻。多個數(shù)據(jù)源中處理起事務(wù)也是非常麻煩的迫靖,而且也沒有必要的,相比之下使用一些補償機制達(dá)到最終用一致性也許是更好的選擇兴使。這個也沒有絕對性的系宜,因需求而異。
這個實現(xiàn)方式網(wǎng)上早已有許多成熟的例子发魄,我也是借鑒了一些盹牧,記錄一下,讓自己熟悉熟悉励幼。如果涉及到一些版權(quán)問題汰寓。請及時聯(lián)系,非常抱歉苹粟。
主要實現(xiàn)方式是注解+AOP
下面總結(jié)下步驟:
- 創(chuàng)建一個動態(tài)數(shù)據(jù)源的對象
/**
* Created by baixiangzhu on 2017/4/19.
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
- 注冊數(shù)據(jù)源有滑。通過spring的Environment對象,讀取配置文件中配置的多個數(shù)據(jù)源配置嵌削。創(chuàng)建動態(tài)數(shù)據(jù)源對象毛好,注冊到spring容器中望艺。
/**
* Created by baixiangzhu on 2017/4/19.
*/
@Slf4j
public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar,EnvironmentAware{
private ConversionService conversionService = new DefaultConversionService();
private PropertyValues dataSourcePropertyValues;
// 如配置文件中未指定數(shù)據(jù)源類型,使用該默認(rèn)值
private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
// 數(shù)據(jù)源
private DataSource defaultDataSource;
private Map<String, DataSource> customDataSources = Maps.newHashMap();
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Map<Object, Object> targetDataSources = Maps.newHashMap();
// 將主數(shù)據(jù)源添加到更多數(shù)據(jù)源中
targetDataSources.put("dataSource", defaultDataSource);
DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
// 添加更多數(shù)據(jù)源
targetDataSources.putAll(customDataSources);
//記錄注冊的數(shù)據(jù)源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;// 默認(rèn)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;
}
/**
* 加載多數(shù)據(jù)源配置
*/
@Override
public void setEnvironment(Environment env) {
initDataSource(env);
}
/**
* 初始化主數(shù)據(jù)源
*/
private void initDataSource(Environment env) {
// 讀取主數(shù)據(jù)源
RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "datasource.");
String dsPrefixs = propertyResolver.getProperty("names");
//獲取所有數(shù)據(jù)源名稱
String[] dataSourceNames = dsPrefixs.split(",");
//綁定主數(shù)據(jù)源
Arrays.stream(dataSourceNames).filter( e -> isMaster(e)).forEach( e-> bindMasterDataSource(env));
//綁定從數(shù)據(jù)源
Arrays.stream(dataSourceNames).filter( e -> !isMaster(e)).forEach(e-> bindSlaveDataSource(e,env));
}
/**
* 綁定數(shù)據(jù)源
* @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);
}
/**
* 綁定默認(rèn)數(shù)據(jù)源
*/
private void bindMasterDataSource(Environment env) {
//綁定默認(rèn)數(shù)據(jù)源
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);
// 排除已經(jīng)設(shè)置的屬性
values.remove("type");
values.remove("driver-class-name");
values.remove("url");
values.remove("username");
values.remove("password");
dataSourcePropertyValues = new MutablePropertyValues(values);
}
dataBinder.bind(dataSourcePropertyValues);
}
}
注:我這里的數(shù)據(jù)庫配置肌访,主數(shù)據(jù)源(即默認(rèn)數(shù)據(jù)源)在config配置中心的application.properties文件中找默,從數(shù)據(jù)源在本地的bootstrap.yml 文件中,從數(shù)據(jù)源可以有多個吼驶,我這里只配置了一個惩激。
數(shù)據(jù)源配置如下:
-
主數(shù)據(jù)源:
Paste_Image.png 從數(shù)據(jù)源
3.創(chuàng)建一個注解,主要作用的標(biāo)記dao使用的數(shù)據(jù)源旨剥∵中溃可以用在方法上和類上,但是不能用在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)建一個維護數(shù)據(jù)源的容器魄咕,主要是記錄數(shù)據(jù)源的名字。就是注解中需要配置的名稱.
/**
* 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類蚌父,去攔截標(biāo)有數(shù)據(jù)源的方法或者類哮兰,根據(jù)注解的數(shù)據(jù)源動態(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("數(shù)據(jù)源[{}]不存在,使用默認(rèn)數(shù)據(jù)源 > {}", 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();
}
}
至此苟弛,重要的實現(xiàn)已經(jīng)完成《鄣模現(xiàn)在來看看如何使用:
- 需要在項目的啟動類中引入動態(tài)注入數(shù)據(jù)源的類
- 在需要使用數(shù)據(jù)的方法或者類升薯,添加數(shù)據(jù)源注解
這下小伙伴們就可以愉快的使用了哈变隔。如果有任何疑問懈叹,歡迎留言