SpringBoot Dynamic 多数据源配置切换 (网上太多坑 自己实践手写)

2023-04-26 11:25:48

首先是配置 我这里是yml文件 properties文件的一样差不多的

一个主库 master 一个从库 slave  我这边只配2个 需要多个的 增加即可

spring:
  datasource:
    master:
      driver-class-name: com.mysql.jdbc.Driver
      url: 自己的数据库连接
      username: 用户名
      password: 密码
    slave:
      driver-class-name: com.mysql.jdbc.Driver
      url: 自己的数据库连接
      username: 用户名
      password: 密码

然后是创建一个自定义注解

import java.lang.annotation.*;
/**
 * 数据源注解
 * @author seven
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface DS {
    String value() default "masterDataSource";
}

继续是在同一个线程 切换数据源的配置类

import org.springframework.stereotype.Component;

/**
 * 当前线程数据源
 * @author seven
 */
@Component
public class DataSourceContextHolder {
    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    // 设置数据源名
    public static void setDB(String dbType) {
        contextHolder.set(dbType);
    }

    // 获取数据源名
    public static String getDB() {
        return contextHolder.get();
    }

    // 清除数据源名
    public static void clearDB() {
        contextHolder.remove();
    }
}

然后是比较重点的配置 简单但重量级 把配置的数据源切入

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

/**
 * 动态数据源
 * @author seven
 */
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContextHolder.getDB();
    }

}

然后就是读取配置 生成DataSource的类了

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

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

/**
 * 动态数据源配置
 * @author seven
 */
@Configuration
public class DynamicDataSourceConfiguration {
    @Primary
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        dataSource.setName("masterDataSource");
        return dataSource;
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    //@ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        dataSource.setName("slaveDataSource");
        return dataSource;
    }

    /**
     * 动态数据源: 通过AOP在不同数据源之间动态切换
     * @return
     */
    @Bean(name = "dynamicDataSource")
    public DataSource dataSource(@Autowired @Qualifier("masterDataSource") DataSource primery,@Autowired @Qualifier("slaveDataSource") DataSource coocon) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(primery);

        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap<Object, Object>(2);
        dsMap.put("masterDataSource", primery);
        dsMap.put("slaveDataSource", coocon);
        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }
    
    @Bean
    public PlatformTransactionManager txManager(DataSource dynamicDataSource) {
        return new DataSourceTransactionManager(dynamicDataSource);
    }

    @Bean
    @ConfigurationProperties(prefix = "mybatis")
    public SqlSessionFactoryBean sqlSessionFactoryBean(@Autowired @Qualifier("dynamicDataSource") DataSource dynamicDataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dynamicDataSource);
        return sqlSessionFactoryBean;
    }

}

最后 当然是Aop 切入了 这边的切入点 是项目的service路径

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 动态数据源AOP切面
 */
@Aspect
@Order(-1)
@Component
public class DynamicDataSourceAspect {

    private Logger logger = LoggerFactory.getLogger(this.getClass());
    //切点
    @Pointcut("execution(* com.xxx.xxx.service..*(..))")
    public void aspect() { }

    @Before("aspect()")
    private void before(JoinPoint point) {
        Object target = point.getTarget();
        String method = point.getSignature().getName();
        Class<?> classz = target.getClass();// 获取目标类
        Class<?>[] parameterTypes = ((MethodSignature) point.getSignature())
                .getMethod().getParameterTypes();
        try {
            Method m = classz.getMethod(method, parameterTypes);
            if (m != null && m.isAnnotationPresent(DS.class)) {
                DS data = m.getAnnotation(DS.class);
                logger.info("method :{},datasource:{}",m.getName() ,data.value());
                DataSourceContextHolder.setDB(data.value());// 数据源放到当前线程中
            }
        } catch (Exception e) {
            logger.error("get datasource error ",e);
            //默认选择master
            DataSourceContextHolder.setDB("masterDataSource");// 数据源放到当前线程中
        }
    }

    @AfterReturning("aspect()")
    public void after(JoinPoint point) {
        DataSourceContextHolder.clearDB();
    }

}

最后 在测试之前 需要在启动类加点东西

//指定aop事务执行顺序,已保证在切换数据源的后面
@EnableTransactionManagement(order = 2)
//排除数据源自动配置
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@Import({DynamicConfiguration.class})

然后  大功告成  测试一波 需要切换到从库的service或类 贴上自定义注解即可

@DS(value = "slaveDataSource")

 

  • 作者:sevenlxn
  • 原文链接:https://blog.csdn.net/sevenlxn/article/details/83990963
    更新时间:2023-04-26 11:25:48