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

首先是配置 我这里是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")

 

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,这是一个比较具体的技术问题。首先,Spring Boot可以通过配置多个数据源来实现多数据源切换。其次,可以使用AOP技术,在方法执行前动态切换数据源。以下是一些关键的代码片段: 1. 在application.properties文件中配置多个数据源 ``` spring.datasource.primary.url=jdbc:mysql://localhost:3306/db1 spring.datasource.primary.username=user1 spring.datasource.primary.password=pass1 spring.datasource.secondary.url=jdbc:mysql://localhost:3306/db2 spring.datasource.secondary.username=user2 spring.datasource.secondary.password=pass2 ``` 2. 创建多个数据源的Bean ``` @Configuration public class DataSourceConfig { @Bean(name = "primaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "secondaryDataSource") @ConfigurationProperties(prefix = "spring.datasource.secondary") public DataSource secondaryDataSource() { return DataSourceBuilder.create().build(); } } ``` 3. 创建动态数据源并设置默认数据源 ``` @Configuration public class DynamicDataSourceConfig { @Autowired @Qualifier("primaryDataSource") private DataSource primaryDataSource; @Autowired @Qualifier("secondaryDataSource") private DataSource secondaryDataSource; @Bean(name = "dynamicDataSource") public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceType.PRIMARY.name(), primaryDataSource); targetDataSources.put(DataSourceType.SECONDARY.name(), secondaryDataSource); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(primaryDataSource); return dynamicDataSource; } @Bean public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dynamicDataSource()); } } ``` 4. 创建AOP切面,在方法执行前根据注解切换数据源 ``` @Aspect @Component public class DataSourceAspect { @Autowired @Qualifier("dynamicDataSource") private DataSource dynamicDataSource; @Pointcut("@annotation(com.example.demo.datasource.DataSource)") public void dataSourcePointCut() {} @Before("dataSourcePointCut()") public void before(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); DataSource dataSource = signature.getMethod().getAnnotation(DataSource.class); if (dataSource != null) { DataSourceType dataSourceType = dataSource.value(); DynamicDataSourceContextHolder.setDataSourceType(dataSourceType.name()); } } @After("dataSourcePointCut()") public void after(JoinPoint joinPoint) { DynamicDataSourceContextHolder.clearDataSourceType(); } } ``` 5. 在需要切换数据源的方法上添加注解 ``` @DataSource(DataSourceType.SECONDARY) public List<User> listUsers() { return jdbcTemplate.query("SELECT * FROM user", new BeanPropertyRowMapper<>(User.class)); } ``` 这样就可以实现动态切换多数据源了。希望能够帮到你!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值