1. 程式人生 > 程式設計 >Springboot專案實現Mysql多資料來源切換的完整例項

Springboot專案實現Mysql多資料來源切換的完整例項

一、分析AbstractRoutingDataSource抽象類原始碼

關注import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource以下變數

@Nullable
private Map<Object,Object> targetDataSources; // 目標資料來源
@Nullable
private Object defaultTargetDataSource; // 預設目標資料來源
@Nullable
private Map<Object,DataSource> resolvedDataSources; // 解析的資料來源
@Nullable
private DataSource resolvedDefaultDataSource; // 解析的預設資料來源

這兩組變數是相互對應的,在熟悉多例項資料來源切換程式碼的不難發現,當有多個數據源的時候,一定要指定一個作為預設的資料來源;

在這裡也同理,當同時初始化多個數據源的時候,需要顯式的呼叫setDefaultTargetDataSource方法指定一個作為預設資料來源;

我們需要關注的是Map<Object,Object> targetDataSources和Map<Object,DataSource> resolvedDataSources;

targetDataSources是暴露給外部程式用來賦值的,而resolvedDataSources是程式內部執行時的依據,因此會有一個賦值的操作;

Springboot專案實現Mysql多資料來源切換的完整例項

根據這段原始碼可以看出,每次執行時,都會遍歷targetDataSources內的所有元素並賦值給resolvedDataSources;這樣如果我們在外部程式新增一個新的資料來源,都會新增到內部使用,從而實現資料來源的動態載入。
繼承該抽象類的時候,必須實現一個抽象方法:protected abstract Object determineCurrentLookupKey(),該方法用於指定到底需要使用哪一個資料來源。

二、實現多資料來源切換和動態資料來源載入

A - 配置檔案資訊

application.yml檔案

server:
 port: 18080
spring:
 datasource:
 type: com.alibaba.druid.pool.DruidDataSource
 druid:
  # 主資料來源
  master-db:
  driverClassName: com.mysql.jdbc.Driver
  url: jdbc:mysql://192.168.223.129:13306/test_master_db?characterEncoding=utf-8
  username: josen
  password: josen
  # 從資料來源
  slave-db:
  driverClassName: com.mysql.jdbc.Driver
  url: jdbc:mysql://192.168.223.129:13306/test_slave_db?characterEncoding=utf-8
  username: josen
  password: josen
mybatis:
 mapper-locations: classpath:mapper/*.xml
logging:
 path: ./logs/mydemo20201105.log
 level:
 com.josen.mydemo20201105: debug

maven 依賴

<dependencies>
 <!-- AOP -->
 <dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
 </dependency>
 <!-- druid -->
 <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.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>2.1.3</version>
 </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>
</dependencies>

B - 編碼實現

1、建立一個DynamicDataSource類,繼承AbstractRoutingDataSource抽象類,實現determineCurrentLookupKey方法,通過該方法指定當前使用哪個資料來源;

2、 在DynamicDataSource類中通過ThreadLocal維護一個全域性資料來源名稱,後續通過修改該名稱實現動態切換;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;

/**
 * @ClassName DynamicDataSource
 * @Description 設定動態資料來源
 * @Author Josen
 * @Date 2020/11/5 14:28
 **/
public class DynamicDataSource extends AbstractRoutingDataSource {
 // 通過ThreadLocal維護一個全域性唯一的map來實現資料來源的動態切換
 private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

 public DynamicDataSource(DataSource defaultTargetDataSource,Map<Object,Object> targetDataSources) {
  super.setDefaultTargetDataSource(defaultTargetDataSource);
  super.setTargetDataSources(targetDataSources);
  super.afterPropertiesSet();
 }

 /**
  * 指定使用哪一個資料來源
  */
 @Override
 protected Object determineCurrentLookupKey() {
  return getDataSource();
 }

 public static void setDataSource(String dataSource) {
  contextHolder.set(dataSource);
 }

 public static String getDataSource() {
  return contextHolder.get();
 }

 public static void clearDataSource() {
  contextHolder.remove();
 }
}

3、建立DynamicDataSourceConfig類,引入application.yaml配置的多個數據源資訊,構建多個數據源;

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @ClassName DynamicDataSourceConfig
 * @Description 引入動態資料來源,構建資料來源
 * @Author Josen
 * @Date 2020/11/5 14:23
 **/
@Configuration
@Component
public class DynamicDataSourceConfig {
 /**
  * 讀取application配置,構建master-db資料來源
  */
 @Bean
 @ConfigurationProperties("spring.datasource.druid.master-db")
 public DataSource myMasterDataSource(){
  return DruidDataSourceBuilder.create().build();
 }

 /**
  * 讀取application配置,構建slave-db資料來源
  */
 @Bean
 @ConfigurationProperties("spring.datasource.druid.slave-db")
 public DataSource mySlaveDataSource(){
  return DruidDataSourceBuilder.create().build();
 }
 /**
  * 讀取application配置,建立動態資料來源
  */
 @Bean
 @Primary
 public DynamicDataSource dataSource(DataSource myMasterDataSource,DataSource mySlaveDataSource) {
  Map<Object,Object> targetDataSources = new HashMap<>();
  targetDataSources.put("master-db",myMasterDataSource);
  targetDataSources.put("slave-db",mySlaveDataSource);
  // myMasterDataSource=預設資料來源
  // targetDataSources=目標資料來源(多個)
  return new DynamicDataSource(myMasterDataSource,targetDataSources);
 }
}

4、 建立MyDataSource自定義註解,後續AOP通過該註解作為切入點,通過獲取使用該註解存入不同的值動態切換指定的資料來源;

import java.lang.annotation.*;
/**
 * 自定義資料來源註解
 * 在需要切換資料來源的Service層方法上新增此註解,指定資料來源名稱
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyDataSource {
 String name() default "";
}

5、 建立DataSourceAspectAOP切面類,以MyDataSource註解為切入點作拓展。在執行被@MyDataSource註解的方法時,獲取該註解傳入的name,並切換到指定資料來源執行,執行完成後切換回預設資料來源;

import com.josen.mydemo20201105.annotation.MyDataSource;
import com.josen.mydemo20201105.datasource.DynamicDataSource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.lang.reflect.Method;

/**
 * @ClassName DataSourceAspect
 * @Description Aop切面類配置
 * @Author Josen
 * @Date 2020/11/5 14:35
 **/
@Aspect
@Component
@Slf4j
public class DataSourceAspect {
 private static final Logger logger = LoggerFactory.getLogger(DataSourceAspect.class);

 /**
  * 設定切入點
  * 只有呼叫@MyDataSource註解的方法才會觸發around
  */
 @Pointcut("@annotation(com.josen.mydemo20201105.annotation.MyDataSource)")
 public void dataSourcePointCut() {
 }

 /**
  * 擷取使用MyDataSource註解的方法,切換指定資料來源
  * 環繞切面:是(前置&後置&返回&異常)通知的結合體,更像是動態代理的整個過程
  * @param point
  */
 @Around("dataSourcePointCut()")
 public Object around(ProceedingJoinPoint point) throws Throwable {
  MethodSignature signature = (MethodSignature) point.getSignature();
  Method method = signature.getMethod();
  logger.info("execute DataSourceAspect around=========>"+method.getName());
  // 1. 獲取自定義註解MyDataSource,檢視是否配置指定資料來源名稱
  MyDataSource dataSource = method.getAnnotation(MyDataSource.class);
  if(dataSource == null){
   // 1.1 使用預設資料來源
   DynamicDataSource.setDataSource("master-db");
  }else {
   // 1.2 使用指定名稱資料來源
   DynamicDataSource.setDataSource(dataSource.name());
   logger.info("使用指定名稱資料來源=========>"+dataSource.name());
  }
  try {
   return point.proceed();
  } finally {
   // 後置處理 - 恢復預設資料來源
   DynamicDataSource.clearDataSource();
  }
 }
}

6、 配置啟動類

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) // 不載入預設資料來源配置
@MapperScan(basePackages = "com.josen.mydemo.mapper")
public class MydemoApplication {
 public static void main(String[] args) {
  SpringApplication.run(MydemoApplication.class,args);
 }
}

7、 到這裡基本上已經完成,剩下的就是測試是否正確切換資料來源了;

Mapper層

@Repository
public interface PermissionMapper {
 List<Permission> findAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.josen.mydemo.mapper.PermissionMapper">
 <select id="findAll" resultType="com.josen.mydemo20201105.pojo.Permission">
  select * from t_permission;
 </select>
</mapper>

Service層

@Service
public class PermissionService {
 @Autowired
 private PermissionMapper permissionMapper;
	// 切換從庫資料來源
 @MyDataSource(name = "slave-db")
 public List<Permission> findSlaveAll(){
  return permissionMapper.findAll();
 }
	// 預設資料來源
 public List<Permission> findAll(){
  return permissionMapper.findAll();
 }
}

Controller層

@RestController
@RequestMapping("/permission")
public class PermissionController {
 @Autowired
 private PermissionService permissionService;

 // 測試獲取預設master-db資料
 @GetMapping("/master")
 public List<Permission> handlerFindAll() {
  List<Permission> list = permissionService.findAll();
  return list;
 }

 // 測試獲取指定slave-db資料
 @GetMapping("/slave")
 public List<Permission> handlerFindAll2() {
  List<Permission> list = permissionService.findSlaveAll();
  return list;
 }
}

C - 測試資料來源切換

Mysql資料

Springboot專案實現Mysql多資料來源切換的完整例項

Springboot專案實現Mysql多資料來源切換的完整例項

介面返回資料

Springboot專案實現Mysql多資料來源切換的完整例項

Springboot專案實現Mysql多資料來源切換的完整例項

Demo原始碼地址:https://gitee.com/taco-gigigi/multiple-data-sources

總結

到此這篇關於Springboot專案實現Mysql多資料來源切換的文章就介紹到這了,更多相關Springboot專案Mysql多資料來源切換內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!