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層調用 。