spring boot 多數據源載入原理
- 2019 年 12 月 16 日
- 筆記
git程式碼:https://gitee.com/wwj912790488/multiple-data-sources
DynamicDataSourceAspect切面 必須定義@Order(-10),保證該aop在@Transaction之前執行
配置如下,分別載入三個資料庫配置
1.利用ImportBeanDefinitionRegistrar和EnvironmentAware 載入註冊多個數據源bean
package org.spring.boot.multiple.ds; import com.alibaba.druid.pool.DruidDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.PropertyValues; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.boot.bind.RelaxedDataBinder; import org.springframework.boot.bind.RelaxedPropertyResolver; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.env.Environment; import org.springframework.core.type.AnnotationMetadata; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; /** * @author donghongchen * @create 2017-09-04 15:34 * <p> * 動態數據源註冊 **/ public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware { private Logger logger = LoggerFactory.getLogger(this.getClass()); //如果配置文件中未指定數據源類型,使用默認值 private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource"; private ConversionService conversionService = new DefaultConversionService(); private PropertyValues dataSourcePropertyValues; //默認數據源 private DataSource defaultDataSource; private Map<String, DataSource> customDataSources = new HashMap<>(); /** * 載入多數據源配置 * * @param environment */ @Override public void setEnvironment(Environment environment) { initDefaultDataSource(environment); initCustomDataSources(environment); } private void initDefaultDataSource(Environment environment) { //讀取主數據源 RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "spring.datasource."); Map<String, Object> 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, environment); } private void initCustomDataSources(Environment environment) { //讀取配置文件獲取更多數據源,也可以通過defaultDataSource讀取資料庫獲取更多數據源 RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(environment, "custom.datasource."); String dsPrefixs = propertyResolver.getProperty("names"); if (null == dsPrefixs || "".equals(dsPrefixs)) { return; } String[] dsPrefixsArr = dsPrefixs.split(","); for (String dsPrefix : dsPrefixsArr) { Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix + "."); DataSource ds = buildDataSource(dsMap); customDataSources.put(dsPrefix, ds); dataBinder(ds, environment); } } private DataSource buildDataSource(Map<String, Object> dsMap) { Object type = dsMap.get("type"); if (type == null) { type = DATASOURCE_TYPE_DEFAULT; } Class<? extends DataSource> dataSourceType; try { 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(); DruidDataSource dataSource = new DruidDataSource(); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(driverClassName); return dataSource; // DataSourceBuilder factory = DataSourceBuilder.create(). // driverClassName(driverClassName).type(dataSourceType).url(url).username(username).password(password); // return factory.build(); } catch (ClassNotFoundException ex) { logger.error(ex.getMessage(), ex); } return null; } private void dataBinder(DataSource dataSource, Environment environment) { RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource); dataBinder.setConversionService(conversionService); dataBinder.setIgnoreNestedProperties(false); dataBinder.setIgnoreInvalidFields(false); dataBinder.setIgnoreUnknownFields(true); if (dataSourcePropertyValues == null) { Map<String, Object> rpr = new RelaxedPropertyResolver(environment, "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) { Map<String, Object> targetDataSource = new HashMap<>(); //將主數據源添加到更多數據源中 targetDataSource.put("dataSource", defaultDataSource); DynamicDataSourceContextHolder.dataSourceIDS.add("dataSource"); //添加更多數據源 targetDataSource.putAll(customDataSources); for (String key : customDataSources.keySet()) { DynamicDataSourceContextHolder.dataSourceIDS.add(key); } //創建DynamicDataSource GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); beanDefinition.setBeanClass(DynamicDataSource.class); beanDefinition.setSynthetic(true); MutablePropertyValues mpv = beanDefinition.getPropertyValues(); //添加屬性 mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource); mpv.addPropertyValue("targetDataSources", targetDataSource); registry.registerBeanDefinition("dataSource", beanDefinition); } }
根據@annotation 去動態切換數據源
package org.spring.boot.multiple.ds; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * @author donghongchen * @create 2017-09-04 14:44 * <p> * 切換數據源Advice **/ @Aspect @Order(-10) //保證該aop在@Transaction之前執行 @Component public class DynamicDataSourceAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * * @Before("@annotation(ds)") * 的意思是:@Before:在方法執行之前進行執行; @annotation(targetDataSource):會攔截註解targetDataSource的方法,否則不攔截; * @param point * @param targetDataSource */ @Before("@annotation(targetDataSource)") public void changeDataSource(JoinPoint point, TargetDataSource targetDataSource){ //獲取當前的指定數據源 String dsID = targetDataSource.value(); //如果不在我們注入的所有的數據源範圍內,輸出警告資訊,系統自動使用默認的數據源 if (!DynamicDataSourceContextHolder.containsDataSource(dsID)){ logger.error("數據源["+dsID+"]不存在,使用默認的數據源 > { " + dsID+", 方法簽名:"+point.getSignature()+"}"); }else { logger.info("Use DataSource: {" +dsID+", 方法簽名:"+point.getSignature() +"}"); //找到的話,那麼設置動態數據源上下文 DynamicDataSourceContextHolder.setDataSourceType(dsID); } } @After("@annotation(targetDataSource)") public void restoreDataSource(JoinPoint point, TargetDataSource targetDataSource){ //方法執行完畢後,銷毀當前數據源資訊,進行垃圾回收 DynamicDataSourceContextHolder.clearDataSourceType(); } }
最後對應到對應的DAO層調用 。