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")

 

DynamicDataSourceContextHolder配合自定义注解使用主要是为了实现数据源的动态切换。在使用过程中,可以通过自定义注解来标记需要切换数据源的方法或类,在运行时根据注解信息来动态选择数据源。 下面是一个简单的示例代码: 首先,定义一个注解类,用于标记需要切换数据源的方法或类: ```java @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default "default"; } ``` 然后,创建一个数据源上下文持有者类 DynamicDataSourceContextHolder,用于保存当前线程所使用的数据源: ```java public class DynamicDataSourceContextHolder { private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public static void setDataSource(String dataSourceName) { contextHolder.set(dataSourceName); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } } ``` 接下来,在切换数据源的方法上添加 DataSource 注解,并在方法执行前后通过 DynamicDataSourceContextHolder 来切换数据源: ```java @DataSource("dataSource2") public void doSomething() { // 切换数据源前,先保存当前的数据源 String currentDataSource = DynamicDataSourceContextHolder.getDataSource(); try { // 切换数据源 DynamicDataSourceContextHolder.setDataSource("dataSource2"); // 执行需要切换数据源的操作 // ... } finally { // 恢复之前保存的数据源 DynamicDataSourceContextHolder.setDataSource(currentDataSource); } } ``` 这样,在使用注解标记的方法或类中,就可以根据注解信息来动态切换数据源了。当方法执行结束后,通过 finally 块来恢复之前保存的数据源,确保切换数据源不会影响到其他方法的使用。 需要注意的是,在实际项目中,需要根据自己的需求来实现数据源切换的逻辑,上述代码只是一个简单的示例。同时,还需要配置相应的数据源切换逻辑,以便使动态数据源切换生效。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值