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";
}