springboot实现主从数据库动态切换(注解式)

1. 创建一个管理数据源key值的类RoutingDataSourceHolder 

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
 
public class RoutingDataSourceHolder {
    private static Logger logger = LogManager.getLogger();
 
    private static final ThreadLocal<String> dataSources = new ThreadLocal<>();
 
    //一个事务内用同一个数据源
    public static void setDataSource(String dataSourceName) {
        if (dataSources.get() == null) {
            dataSources.set(dataSourceName);
            logger.info("设置数据源:{}", dataSourceName);
        }
    }
 
    public static String getDataSource() {
        return dataSources.get();
    }
 
    public static void clearDataSource() {
        dataSources.remove();
    }
}

代码设置了一个事务内使用同一个数据源。

2. 创建一个类继承AbstractRoutingDataSource

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
 
public class RoutingDataSource extends AbstractRoutingDataSource {
    private Logger logger = LogManager.getLogger();
 
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSource = RoutingDataSourceHolder.getDataSource();
        logger.info("使用数据源:{}", dataSource);
        return dataSource;
    }
}

重写 determineCurrentLookupKey方法,返回要使用的数据源key值。 

以上两个类解决了动态数据源key值的问题,下面处理初始化targetDataSources对象。

3. 配置主从数据库类DataSourceConfigurer

1. DataSourceConfigurer

import com.alibaba.druid.pool.DruidDataSource;
import com.custom.common.utils.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
 
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
 
/**
 * 配置主从数据库
 */
@Configuration
public class DataSourceConfigurer {
    private Logger logger = LogManager.getLogger();
 
    public final static String MASTER_DATASOURCE = "masterDataSource";
    public final static String SLAVE_DATASOURCE = "slaveDataSource";
 
    @Bean(MASTER_DATASOURCE)
    @ConfigurationProperties(prefix = "spring.datasource")
    public DruidDataSource masterDataSource(DataSourceProperties properties) {
        DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
        logger.info("配置主数据库:{}", build);
        return build;
    }
 
    @Bean(SLAVE_DATASOURCE)
    @ConfigurationProperties(prefix = "spring.slave-datasource")
    public DruidDataSource slaveDataSource(DataSourceProperties properties) {
        DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
        logger.info("配置从数据库:{}", build);
        return build;
    }
 
    /**
     * Primary 优先使用该Bean
     * DependsOn 先执行主从数据库的配置
     * Qualifier 指定使用哪个Bean
     *
     * @param masterDataSource
     * @param slaveDataSource
     * @return
     */
    @Bean
    @Primary
    @DependsOn(value = {MASTER_DATASOURCE, SLAVE_DATASOURCE})
    public DataSource routingDataSource(@Qualifier(MASTER_DATASOURCE) DruidDataSource masterDataSource,
                                        @Qualifier(SLAVE_DATASOURCE) DruidDataSource slaveDataSource) {
        if (StringUtils.isBlank(slaveDataSource.getUrl())) {
            logger.info("没有配置从数据库,默认使用主数据库");
            return masterDataSource;
        }
        Map<Object, Object> map = new HashMap<>();
        map.put(DataSourceConfigurer.MASTER_DATASOURCE, masterDataSource);
        map.put(DataSourceConfigurer.SLAVE_DATASOURCE, slaveDataSource);
        RoutingDataSource routing = new RoutingDataSource();
        //设置动态数据源
        routing.setTargetDataSources(map);
        //设置默认数据源
        routing.setDefaultTargetDataSource(masterDataSource);
        logger.info("主从数据库配置完成");
        return routing;
    }
}

设置初始化targetDataSources对象关键代码

Map<Object, Object> map = new HashMap<>();
map.put(DataSourceConfigurer.MASTER_DATASOURCE, masterDataSource);
map.put(DataSourceConfigurer.SLAVE_DATASOURCE, slaveDataSource);
RoutingDataSource routing = new RoutingDataSource();
//设置动态数据源
routing.setTargetDataSources(map);
//设置默认数据源
routing.setDefaultTargetDataSource(masterDataSource);

2. application.properties

# ----------------------------------------
# 主数据库
# ----------------------------------------
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/custom?useunicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
 
# ----------------------------------------
# 从数据库
# ----------------------------------------
spring.slave-datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.slave-datasource.url=jdbc:mysql://127.0.0.1:3309/custom?useunicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
spring.slave-datasource.username=root
spring.slave-datasource.password=root
spring.slave-datasource.driver-class-name=com.mysql.cj.jdbc.Driver

一个配置类处理了targetDataSources对象的初始化.

那问题都处理了,那具体要怎么使用呢,关键就是在事务之前调用RoutingDataSourceHolder.setDataSource()方法就可以了。我们写一个aop实现吧。

4. 创建aop注解和类

1. DataSourceWith

import java.lang.annotation.*;
 
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
public @interface DataSourceWith {
    String key() default "";
}

2.DataSourceWithAspect

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
 
import java.lang.reflect.Method;
 
@Aspect
@Order(-1)// 保证该AOP在@Transactional之前运行
@Component
public class DataSourceWithAspect {
 
    /**
     * 使用DataSourceWith注解就拦截
     */
    @Pointcut("@annotation(com.custom.configure.datasource.DataSourceWith)||@within(com.custom.configure.datasource.DataSourceWith)")
    public void doPointcut() {
 
    }
 
    /**
     * 方法前,为了在事务前设置
     */
    @Before("doPointcut()")
    public void doBefore(JoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 获取注解对象
        DataSourceWith dataSource = method.getAnnotation(DataSourceWith.class);
        if (dataSource == null) {
            //方法没有就获取类上的
            dataSource = method.getDeclaringClass().getAnnotation(DataSourceWith.class);
        }
        String key = dataSource.key();
        RoutingDataSourceHolder.setDataSource(key);
    }
 
    @After("doPointcut()")
    public void doAfter(JoinPoint joinPoint) {
        RoutingDataSourceHolder.clearDataSource();
    }
 
}

3.使用和效果

@DataSourceWith在方法上或者类上都可以。 

    /**
     * 获取部门列表
     **/
    @DataSourceWith(key = DataSourceConfigurer.SLAVE_DATASOURCE)
    public List<Dept> findDeptTree() {
        logger.debug("获取部门树数据");
        List<Dept> deptList = new ArrayList<>();
        return deptList;
    }

结果:动态切换成功

原文链接:https://blog.csdn.net/m0_68615056/article/details/123738282

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李巴巴

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值