SpringBoot動態數據源配置


SpringBoot動態數據源配置

序:數據源動態切換流程圖如下:

 

 

 

1:pom.xml文件依賴聲明

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.1.4</version>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>8.0.12</version>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2:application.yml配置文件

配置多個數據源信息

server:
port: 9000

dynamic:
datasource:
  ds1: # 數據源1
    jdbc-url: jdbc:mysql://localhost:3306/test1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
  ds2: # 數據源2
    jdbc-url: jdbc:mysql://localhost:3306/test2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
mapper-locations: classpath:mapper/*.xml
configuration:
  map-underscore-to-camel-case: true

3:數據源的配置到Spring管理

/**
* description 數據源配置類
*
* @author liekkas 2021/08/21 12:33
*/
@Configuration
public class DynamicDataSourceConfig {

  @Bean(name = "dataSource1")
  @ConfigurationProperties(prefix = "dynamic.datasource.ds1")
  public DataSource dateSource1() {
      return DataSourceBuilder.create().build();
  }

  @Bean(name = "dataSource2")
  @ConfigurationProperties(prefix = "dynamic.datasource.ds2")
  public DataSource dateSource2() {
      return DataSourceBuilder.create().build();
  }

  @Bean
  @Primary
  public DynamicRoutingDataSource dynamicDataSource() {
      DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();

//設置默認的數據源
      dynamicRoutingDataSource.setDefaultTargetDataSource(dateSource1());
      Map<Object, Object> targetDataSources = new HashMap<>(4);

      targetDataSources.put("dataSource1", dateSource1());
      targetDataSources.put("dataSource2", dateSource2());
//設置數據源
      dynamicRoutingDataSource.setTargetDataSources(targetDataSources);
      dynamicRoutingDataSource.afterPropertiesSet();
      DynamicDataSourceContextHolder.dataSourceSet.addAll(targetDataSources.keySet());
      return dynamicRoutingDataSource;
  }
}

注意:@ConfigurationProperties注解的prefix的值和配置文件application.yml必須要保持一致

3:配置數據源上下文

我們需要新建一個數據源上下文,用戶記錄當前線程使用的數據源是什么,以及記錄所有注冊成功的數據源的集合。對於線程級別的私有變量,我們首先ThreadLocal來實現。

/**
* description 設置當前線程數據源上下文類
*
* @author liekkas 2021/08/21 12:37
*/
public class DynamicDataSourceContextHolder {
  private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);

  /**
    * 存儲已經注冊的數據源的key
    */
  public static Set<Object> dataSourceSet = new HashSet<>();

  /**
    * 線程級別的私有變量
    */
  private static final ThreadLocal<String> HOLDER = new ThreadLocal<>();

  public static String getDataSourceRouterKey() {
      return HOLDER.get();
  }

  public static void setDataSourceRouterKey(String dataSourceRouterKey) {
      logger.info("當前數據源正在切換中......");
      HOLDER.set(dataSourceRouterKey);
  }

  /**
    * 設置數據源之前一定要先移除
    */
  public static void removeDataSourceRouterKey() {
      HOLDER.remove();
  }

  /**
    * 判斷指定dataSource當前是否存在
    *
    * @param dataSource dataSource
    * @return boolean
    */
  public static boolean isExistDataSource(Object dataSource) {
      return dataSourceSet.contains(dataSource);
  }

}

4:動態數據源路由

第三步我們以及新建了數據源上下文,用於存儲我們當前線程的數據源,那么怎么通知spring用當前的數據源,spring提供一個接口,名為AbstractRoutingDataSource的抽象類,我們只需要重寫determineCurrentLookupKey方法就可以,這個方法看名字就知道,就是返回當前線程的數據源,那我們只需要從我們剛剛的數據源上下文中取出當前數據源即可

/**
* description 數據源動態路由
*
* @author liekkas 2021/08/21 12:39
*/
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

  private static final Logger logger = LoggerFactory.getLogger(DynamicRoutingDataSource.class);

  @Override
  protected Object determineCurrentLookupKey() {
      String dataSourceName = DynamicDataSourceContextHolder.getDataSourceRouterKey();
      logger.info("當前數據源是:{}", dataSourceName);
      return DynamicDataSourceContextHolder.getDataSourceRouterKey();
  }
}:

5:數據源動態切換注解

/**
* description 動態切換數據源注解
* 該注解可以同時作用於類和方法上,如果類和方法注解同時存在,則方法的注解優先級高於類上的注解優先級
* 也就是說類和方法同時存在該注解的話,則會使用方法的數據源
*
* @author liekkas 2021/08/21 12:40
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
  String value() default "dataSource1";
}:

6:利用aop實現數據源動態切換

/**
* description 動態切換數據源切面
*
* @author liekkas 2021/08/21 12:42
*/
@Aspect
@Component
@Order(-1)
public class DynamicDataSourceAspect {
  private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

  /**
    * 同時攔截標注DataSource的注解類上或者方法
    */
  @Pointcut(value = "@within(com.liekkas.config.datasource.DataSource) || @annotation(com.liekkas.config.datasource.DataSource)")
  public void datasource() {
  }


  @Before("datasource()")
  public void changeDataSource(JoinPoint joinPoint) {

      Class<?> klass = joinPoint.getTarget().getClass();

      MethodSignature signature = (MethodSignature) joinPoint.getSignature();
      Method method = signature.getMethod();

      DataSource dataSource;
      if (method.isAnnotationPresent(DataSource.class)) {
          dataSource = method.getAnnotation(DataSource.class);
      } else {
          dataSource = klass.getAnnotation(DataSource.class);
      }

      //判斷注解中的數據源是否在已注冊的數據源中
      if (DynamicDataSourceContextHolder.isExistDataSource(dataSource.value())) {
          DynamicDataSourceContextHolder.setDataSourceRouterKey(dataSource.value());
          logger.info("數據源設置為 > {}", dataSource.value());
      } else {
          throw new RuntimeException("數據源[" + dataSource.value() + "]不存在");
      }
  }

  @After("datasource()")
  public void restoreDataSource() {
      DynamicDataSourceContextHolder.removeDataSourceRouterKey();
  }
}

7:測試

7.1:controller

/**
* description
*
* @author liekkas 2021/01/09 15:47
*/
@RestController
public class PersonController {

  @Resource
  private PersonService personService;

  @GetMapping("person")
  public List<Person> findPerson() {
      return personService.findPerson();
  }

  @GetMapping("student")
  public List<Student> findStudent() {
      return personService.findStudent();
  }
}

7.2: service

public interface PersonService {
  /**
    * findPerson
    *
    * @return list
    */
  List<Person> findPerson();
  /**
    * findStudent
    *
    * @return list
    */
  List<Student> findStudent();
}

7.3: serviceImpl

@Service
@DataSource("dataSource1")
public class PersonServiceImpl implements PersonService {

  @Resource
  private PersonMapper personMapper;

  @Override
  public List<Person> findPerson() {
      return personMapper.findPerson();
  }

  @Override
  @DataSource("dataSource2")
  public List<Student> findStudent() {
      return personMapper.findStudent();
  }
}

由於篇幅關系,mapper以及實體類不再描述

測試接口調用

http://localhost:9000/person

 

 

 

http://localhost:9000/student

 

 

 

至此數據源動態切換已經完成。

小結:數據源動態切換主要原理就是實現了Spring提供的AbstractRoutingDataSource類,然后我們就可以根據自己的需要利用aop和自定義注解,ThreadLocal來實現動態切換使用哪一個數據源了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM