SpringBoot配置多資料來源

邢帅杰發表於2024-07-11
參考:https://blog.csdn.net/qq_37759895/article/details/135742006
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>
    <groupId>com.xcg</groupId>
    <artifactId>xcg-back</artifactId>
    <packaging>jar</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>xcg-back</name>

    <properties>
        <elasticsearch.version>7.4.1</elasticsearch.version>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.7.10</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.10</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>2.7.10</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.14.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.34</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.51</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.23</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>19.3.0.0</version>
        </dependency>
    </dependencies>
</project>

application.yml

spring:
  datasource:
    db01:
      db-type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://xxx:3306/testdb1?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&failOverReadOnly=false
      username: root
      password: root
      validation-query: SELECT 1 FROM DUAL #測試連線是否可用的SQL語句
      initial-size: 10 #資料庫連線池初始化連線數量
      min-idle: 10 #資料庫連線池最小連線數量
      max-active: 30 #資料庫連線池最大連線數量
      max-wait: 30000 # 配置獲取連線等待超時的時間 30000毫秒(30秒)
    db02:
      db-type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://xxx:3306/testdb2?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8&autoReconnect=true&failOverReadOnly=false
      username: root
      password: root
      validation-query: SELECT 1 FROM DUAL #測試連線是否可用的SQL語句
      initial-size: 10 #資料庫連線池初始化連線數量
      min-idle: 10 #資料庫連線池最小連線數量
      max-active: 30 #資料庫連線池最大連線數量
      max-wait: 30000 # 配置獲取連線等待超時的時間 30000毫秒(30秒)

配置檔案繫結類,把yml中的配置轉為類map繫結資料來源

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Map;
@Data
@ConfigurationProperties(prefix = "spring")
public class DynamicDatasourceProperties {
    private Map<String, DruidDataSource> datasource;
}

資料來源切換類,使用ThreadLocal,每個執行緒使用自己的資料來源副本。

public class DynamicDataSourceContextHolder {
    /**
     * 動態資料來源名稱上下文
     */
    private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>();
    /**
     * 設定/切換資料來源
     */
    public static void setContextKey(String key) {
        DATASOURCE_CONTEXT_KEY_HOLDER.set(key);
    }
    /**
     * 獲取資料來源名稱
     */
    public static String getContextKey() {
        String key = DATASOURCE_CONTEXT_KEY_HOLDER.get();
        return key == null ? DataSourceConstants.DS_KEY_MASTER : key;
    }
    /**
     * 刪除當前資料來源名稱
     */
    public static void removeContextKey() {
        DATASOURCE_CONTEXT_KEY_HOLDER.remove();
    }
}

繼承AbstractRoutingDataSource,重寫determineCurrentLookupKey()

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 動態資料來源
 * */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getContextKey();
    }
}

配置資料來源

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;

@Slf4j
@Configuration
public class DataSourceConfig {

    @Configuration
    @EnableConfigurationProperties(DynamicDatasourceProperties.class)
    @MapperScan(basePackages = "com.xcg.mapper", sqlSessionTemplateRef  = "otherSqlSessionTemplate")
    public static class DynamicDatasourceConfiguration {

        @Resource
        private DynamicDatasourceProperties dynamicDatasourceProperties;
        @Bean(name = "otherDataSource")
        public DynamicDataSource otherDataSource(){
            HashMap<Object, Object> dataSourceMap = new HashMap<>(dynamicDatasourceProperties.getDatasource());
            DynamicDataSource dynamicDatasource = new DynamicDataSource();
            dynamicDatasource.setTargetDataSources(dataSourceMap);
            dynamicDatasource.setDefaultTargetDataSource(dataSourceMap.get("master"));
            return dynamicDatasource;
        }

        @Bean(name = "otherTransactionManager")
        public DataSourceTransactionManager otherTransactionManager(@Qualifier("otherDataSource") DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }

        @Bean(name = "otherSqlSessionFactory")
        public SqlSessionFactory otherSqlSessionFactory(@Qualifier("otherDataSource") DataSource dataSource) throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dataSource);
            bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
            bean.setTypeAliasesPackage("com.xcg.model");
            return bean.getObject();
        }

        @Bean(name = "otherSqlSessionTemplate")
        public SqlSessionTemplate otherSqlSessionTemplate(@Qualifier("otherSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }
}

aop

import com.xcg.annotation.DS;
import com.xcg.config.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
public class DynamicDataSourceAspect {
    //定義切入點
    //@Pointcut("execution(* com.xcg.mapper.*.*(..))")   com.railway.mapper 包下所有方法,不管什麼返回值和引數。
    //@Pointcut("@annotation(com.xcg.annotation.DS)") 所有使用了 DS 註解的地方
    @Pointcut("@annotation(com.xcg.annotation.DS)")
    public void pointcut() {
    }
    //可以多個 && @annotation(permissions) && @annotation(ds)
//    @Around("pointcut()")
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //資料來源名稱
        String dsKey = getDSAnnotation(joinPoint);
        DynamicDataSourceContextHolder.setContextKey(dsKey);
        try {
            return joinPoint.proceed();
        } finally {
            DynamicDataSourceContextHolder.removeContextKey();
        }
    }

    /**
     * 獲取第一個引數作為資料來源
     */
    private String getDSAnnotation(ProceedingJoinPoint joinPoint) {
        //DS設定到類上面沒用,只能設定到mapper的方法上面,第一個引數指定資料來源。
        String dsKey = null;
        Object[] args = joinPoint.getArgs();
        if (null != args[0]) {
            String key = args[0].toString();
            dsKey = "db" + key;
        }
        if (StringUtils.isEmpty(dsKey)) {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            dsKey = methodSignature.getMethod().getAnnotation(DS.class).value();
        }
        return dsKey;
    }
}

註解

import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DS {
    String value() default "01";
}

相關文章