Springboot之多數據源的配置使用

  • 2019 年 10 月 8 日
  • 筆記

引入

現在的企業服務逐漸地呈現出數據的指數級增長趨勢,無論從數據庫的選型還是搭建,大多數的團隊都開始考慮多樣化的數據庫來支撐存儲服務。例如分佈式數據庫、Nosql數據庫、內存數據庫、關係型數據庫等等。再到後端開發來說,服務的增多,必定需要考慮到多數據源的切換使用來兼容服務之間的調用。為解決這一難題,今天就來分享一個關於多數據源的切換使用配置。

使用前提:

  • JDK8+
  • Springboot
  • IDEA
  • Mysql5.5+
  • lombok

02

具體配置

使用spring AOP的思想,在進入orm之前,進行DataSource的Type切換。 添加maven依賴,我使用的是springboot-2.0.5版本。

<dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-jdbc</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-aop</artifactId>          </dependency>          <dependency>              <groupId>com.alibaba</groupId>              <artifactId>druid-spring-boot-starter</artifactId>              <version>1.1.10</version>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-web</artifactId>          </dependency>          <dependency>              <groupId>org.springframework.boot</groupId>              <artifactId>spring-boot-starter-data-jpa</artifactId>          </dependency>          <dependency>              <groupId>mysql</groupId>              <artifactId>mysql-connector-java</artifactId>              <scope>runtime</scope>          </dependency>          <dependency>              <groupId>org.projectlombok</groupId>              <artifactId>lombok</artifactId>              <optional>true</optional>          </dependency>

創建application.yml

server:    port: 8080    spring:    profiles:      active: dev    application:      name: @pom.artifactId@      jpa:      generate-ddl: false      show-sql: true      hibernate:        ddl-auto: none      datasource:      default:        url: jdbc:mysql://localhost:3306/bboyhan?characterEncoding=UTF-8&useUnicode=true&useSSL=false        username: root        password: root        driver: com.mysql.jdbc.Driver        type: com.alibaba.druid.pool.DruidDataSource        names: tb1,tb2      tb1:        url: jdbc:mysql://localhost:3306/tb1?characterEncoding=UTF-8&useUnicode=true&useSSL=false        username: root        password: root        driver: com.mysql.jdbc.Driver      tb2:        url: jdbc:mysql://localhost:3306/tb2?characterEncoding=UTF-8&useUnicode=true&useSSL=false        username: root        password: root        driver: com.mysql.jdbc.Driver

創建註解類@DbName

import java.lang.annotation.*;  /**   * @Auther: bboyHan   * @Date: 2019/1/22 18:12   * @Description:   */  @Target({ElementType.TYPE, ElementType.METHOD})  @Retention(RetentionPolicy.RUNTIME)  @Documented  public @interface DbName {      String value();  }

創建aop切面類ChooseDbAspect

import com.jkzx.common.annotation.DbName;  import com.jkzx.common.config.dbsource.DynamicDataSourceContextHolder;  import lombok.extern.slf4j.Slf4j;  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.aspectj.lang.annotation.Pointcut;  import org.springframework.core.annotation.Order;  import org.springframework.stereotype.Component;  /**   * @Auther: bboyHan   * @Date: 2019/1/22 18:13   * @Description:   */  @Component  @Order(-1)//保證在@Transactional之前執行  @Aspect  @Slf4j  public class ChooseDbAspect {        @Pointcut("@annotation(com.bboyhan.annotation.DbName)")      public void chooseDbPointCut(){}        //改變數據源      @Before("@annotation(dbName)")      public void changeDataSource(JoinPoint joinPoint, DbName dbName) {          String dbid = dbName.value();            if (!DynamicDataSourceContextHolder.isContainsDataSource(dbid)) {              //joinPoint.getSignature() :獲取連接點的方法簽名對象              log.error("數據源 " + dbid + " 不存在使用默認的數據源 -> " + joinPoint.getSignature());          } else {              log.debug("使用數據源:" + dbid);              DynamicDataSourceContextHolder.setDataSourceType(dbid);          }      }        @After("@annotation(dbName)")      public void clearDataSource(JoinPoint joinPoint, DbName dbName) {          log.debug("清除數據源 " + dbName.value() + " ! - start");          DynamicDataSourceContextHolder.clearDataSourceType();      }    }

使用ThreadLocal創建一個線程安全的類

import lombok.extern.slf4j.Slf4j;    import java.util.ArrayList;  import java.util.List;    /**   * @Auther: bboyHan   * @Date: 2019/1/22 18:13   * @Description:   */  @Slf4j  public class DynamicDataSourceContextHolder {        //存放當前線程使用的數據源類型信息      private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();      //存放數據源id      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();          log.info("清除數據源:{}", contextHolder.get() + " - end");      }        //判斷當前數據源是否存在      public static boolean isContainsDataSource(String dataSourceId) {          return dataSourceIds.contains(dataSourceId);      }  }

創建一個Register實現ImportBeanDefinitionRegistrar, EnvironmentAware

import lombok.extern.slf4j.Slf4j;  import org.springframework.beans.MutablePropertyValues;  import org.springframework.beans.factory.support.BeanDefinitionRegistry;  import org.springframework.beans.factory.support.GenericBeanDefinition;  import org.springframework.boot.jdbc.DataSourceBuilder;  import org.springframework.context.EnvironmentAware;  import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;  import org.springframework.core.env.Environment;  import org.springframework.core.type.AnnotationMetadata;    import javax.sql.DataSource;  import java.util.HashMap;  import java.util.Map;    /**   * @Auther: bboyHan   * @Date: 2019/1/22 18:19   * @Description:   */  @Slf4j  public class DynamicDataSourceRegister implements ImportBeanDefinitionRegistrar, EnvironmentAware {          //指定默認數據源(springboot2.0默認數據源是hikari,在這裡我使用DruidDataSource)      private static final String DATASOURCE_TYPE_DEFAULT = "com.alibaba.druid.pool.DruidDataSource";      //默認數據源      private DataSource defaultDataSource;      //用戶自定義數據源      private Map<String, DataSource> slaveDataSources = new HashMap<>();        @Override      public void setEnvironment(Environment environment) {          initDefaultDataSource(environment);          initMutilDataSources(environment);      }        private void initDefaultDataSource(Environment env) {          // 讀取主數據源,解析yml文件          Map<String, Object> dsMap = new HashMap<>();          dsMap.put("driver", env.getProperty("spring.datasource.default.driver"));          dsMap.put("url", env.getProperty("spring.datasource.default.url"));          dsMap.put("username", env.getProperty("spring.datasource.default.username"));          dsMap.put("password", env.getProperty("spring.datasource.default.password"));          dsMap.put("type", env.getProperty("spring.datasource.default.type"));          defaultDataSource = buildDataSource(dsMap);      }          private void initMutilDataSources(Environment env) {          // 讀取配置文件獲取更多數據源          String dsPrefixs = env.getProperty("spring.datasource.names");          for (String dsPrefix : dsPrefixs.split(",")) {              // 多個數據源              Map<String, Object> dsMap = new HashMap<>();              dsMap.put("driver", env.getProperty("spring.datasource." + dsPrefix + ".driver"));              dsMap.put("url", env.getProperty("spring.datasource." + dsPrefix + ".url"));              dsMap.put("username", env.getProperty("spring.datasource." + dsPrefix + ".username"));              dsMap.put("password", env.getProperty("spring.datasource." + dsPrefix + ".password"));              DataSource ds = buildDataSource(dsMap);              slaveDataSources.put(dsPrefix, ds);          }      }        @Override      public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {          Map<Object, Object> targetDataSources = new HashMap<Object, Object>();          //添加默認數據源          targetDataSources.put("dataSource", this.defaultDataSource);          DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");          //添加其他數據源          targetDataSources.putAll(slaveDataSources);          DynamicDataSourceContextHolder.dataSourceIds.addAll(slaveDataSources.keySet());            //創建DynamicDataSource          GenericBeanDefinition beanDefinition = new GenericBeanDefinition();          beanDefinition.setBeanClass(DynamicDataSource.class);          beanDefinition.setSynthetic(true);          MutablePropertyValues mpv = beanDefinition.getPropertyValues();          mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);          mpv.addPropertyValue("targetDataSources", targetDataSources);          //註冊 - BeanDefinitionRegistry          beanDefinitionRegistry.registerBeanDefinition("dataSource", beanDefinition);            log.info("Dynamic DataSource Registry");      }        public DataSource buildDataSource(Map<String, Object> dataSourceMap) {          try {              Object type = dataSourceMap.get("type");              if (type == null) {                  type = DATASOURCE_TYPE_DEFAULT;// 默認DataSource              }                Class<? extends DataSource> dataSourceType;              dataSourceType = (Class<? extends DataSource>) Class.forName((String) type);              String driverClassName = dataSourceMap.get("driver").toString();              String url = dataSourceMap.get("url").toString();              String username = dataSourceMap.get("username").toString();              String password = dataSourceMap.get("password").toString();              // 自定義DataSource配置              DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)                      .username(username).password(password).type(dataSourceType);              return factory.build();          } catch (ClassNotFoundException e) {              e.printStackTrace();          }          return null;      }  }

創建一個DynamicDataSource重寫AbstractRoutingDataSource的determineCurrentLookupKey()方法

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;    /**   * @Auther: bboyHan   * @Date: 2019/1/22 18:20   * @Description:   */  public class DynamicDataSource extends AbstractRoutingDataSource {      @Override      protected Object determineCurrentLookupKey() {          return DynamicDataSourceContextHolder.getDataSourceType();      }  }

0

3

測試

略,如果不明白的地方,可以識別文末二維碼加小編諮詢。

文章部分資源來自於網絡,在此鳴謝。