Spring Boot + Druid动态数据源切换(注解实现)

Spring Boot2.3.2 + Druid1.1.22

druid jar包

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid-version}</version>
        </dependency>

properties 配置文件

#mysql

spring.datasource.druid.ds1.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.druid.ds1.url=jdbc:mysql://ip:3306/数据库名?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true
spring.datasource.druid.ds1.username=用户名
spring.datasource.druid.ds1.password=密码
spring.datasource.druid.ds1.type=com.alibaba.druid.pool.DruidDataSource


spring.datasource.druid.ds2.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.druid.ds2.url=jdbc:mysql:ip:3306/数据库名?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai&allowMultiQueries=true
spring.datasource.druid.ds2.username=用户名
spring.datasource.druid.ds2.password=密码
spring.datasource.druid.ds2.type=com.alibaba.druid.pool.DruidDataSource
#config druid
#连接池的设置
#初始化时建立物理连接的个数
spring.datasource.druid.initialSize=5
#最小连接池数量
spring.datasource.druid.minIdle=5
#最大连接池数量 maxIdle已经不再使用
spring.datasource.druid.maxActive=20
#获取连接时最大等待时间,单位毫秒
spring.datasource.druid.max-wait=60000
#申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.druid.test-while-idle=true
#既作为检测的间隔时间又作为testWhileIdel执行的依据
spring.datasource.druid.time-between-eviction-runs-millis=60000
#销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
spring.datasource.druid.min-evictable-idle-time-millis=30000
#用来检测连接是否有效的sql 必须是一个查询语句
#mysql中为 select 'x'
#oracle中为 select 1 from dual
spring.datasource.druid.validation-query=select 'x'
#申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.druid.test-on-borrow=false
#归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.druid.test-on-return=false
#当数据库抛出不可恢复的异常时,抛弃该连接
#spring.datasource.druid.exception-sorter=true
#是否缓存preparedStatement,mysql5.5+建议开启
#spring.datasource.druid.pool-prepared-statements=true
#当值大于0时poolPreparedStatements会自动修改为true
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
#配置扩展插件
#spring.datasource.druid.filters=stat,wall
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#合并多个DruidDataSource的监控数据
spring.datasource.druid.use-global-data-source-stat=true
#设置访问druid监控页的账号和密码,默认没有
spring.datasource.druid.stat-view-servlet.login-username=test
spring.datasource.druid.stat-view-servlet.login-password=123456
spring.datasource.druid.stat-view-servlet.enabled=true
spring.datasource.druid.stat-view-servlet.url-pattern=/druid/*
spring.datasource.druid.stat-view-servlet.reset-enable=true
spring.datasource.druid.stat-view-servlet.allow=
spring.datasource.druid.stat-view-servlet.deny=
spring.datasource.druid.multiStatementAllow=true

启动文件

@SpringBootApplication(
        exclude = DataSourceAutoConfiguration.class
        )
@EnableTransactionManagement
@EnableAspectJAutoProxy(exposeProxy = true)
@Import(DataSourceConfig.class)
public class AdminApplication {

    public static void main(String[] args) {
        SpringApplication.run(AdminApplication.class, args);
    }

}

多数据源配置

import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {
    /**
     * 数据源1
     * @return
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.ds1")
    public DataSource firstDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 数据源2
     * @return
     */
    @Bean
    @ConfigurationProperties("spring.datasource.druid.ds2")
    public DataSource secondDataSource(){
        return DruidDataSourceBuilder.create().build();
    }

    /**
     *
     * @param firstDataSource
     * @param secondDataSource
     * @return
     * Primary 是默认的数据源
     * DynamicDataSource用来坐数据源的切换
     */
    @Bean
    @Primary
    public DynamicDataSource dataSource(DataSource firstDataSource, DataSource secondDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceNames.FIRST, firstDataSource);
        targetDataSources.put(DataSourceNames.SECOND, secondDataSource);
        return new DynamicDataSource(firstDataSource, targetDataSources);
    }
}

多数据源枚举

public class DataSourceNames {
    static String FIRST = "ds1";
    static String SECOND = "ds2";
}

动态数据源

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import javax.sql.DataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {
    //默认数据源
    public static final String DEFAULT_DS = "ds1";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    /**
     * 设置多数据源和默认的方法
     * @param defaultTargetDataSource
     * @param targetDataSources
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    /**
     * 实现AbstractRoutingDataSource并重写determineCurrentLookupKey()方法
     * 就是这个方法获取数据源的
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    /**
     * 设置key
     * @param dataSource
     */
    public static void setDataSource(String dataSource) {
        contextHolder.set(dataSource);
    }

    /**
     * 获取key
     * @return
     */
    public static String getDataSource() {
        return contextHolder.get();
    }

    /**
     * 用了就清除
     */
    public static void clearDataSource() {
        contextHolder.remove();
    }

}
多数据源用注解来切换
import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "ds1";
}

import java.lang.reflect.Method;

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.reflect.MethodSignature;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DynamicDataSourceAspect {
    /**
     * 注解之前调用
     * @param point
     */
    @Before("@annotation(DataSource)")
    public void beforeSwitchDS(JoinPoint point){
        //获得当前访问的class
        Class<?> className = point.getTarget().getClass();
        //获得访问的方法名
        String methodName = point.getSignature().getName();
        //得到方法的参数的类型
        Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
        String dataSource = DynamicDataSource.DEFAULT_DS;
        try {
            // 得到访问的方法对象
            Method method = className.getMethod(methodName, argClass);
            // 判断是否存在@DS注解
            if (method.isAnnotationPresent(DataSource.class)) {
                DataSource annotation = method.getAnnotation(DataSource.class);
                // 取出注解中的数据源名
                dataSource = annotation.value();
                DynamicDataSource.setDataSource(dataSource);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 注解之后调用
     * @param point
     */
    @After("@annotation(DataSource)")
    public void afterSwitchDS(JoinPoint point){
        DynamicDataSource.clearDataSource();
    }
}

然后方法下面加如下注解就可以了


版权声明:本文为qq_38999509原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。