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


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

    1. import org.apache.logging.log4j.LogManager;
    2. import org.apache.logging.log4j.Logger;
    3.  
    4. public class RoutingDataSourceHolder {
    5.     private static Logger logger = LogManager.getLogger();
    6.  
    7.     private static final ThreadLocal dataSources = new ThreadLocal<>();
    8.  
    9.     //一个事务内用同一个数据源
    10.     public static void setDataSource(String dataSourceName) {
    11.         if (dataSources.get() == null) {
    12.             dataSources.set(dataSourceName);
    13.             logger.info("设置数据源:{}", dataSourceName);
    14.         }
    15.     }
    16.  
    17.     public static String getDataSource() {
    18.         return dataSources.get();
    19.     }
    20.  
    21.     public static void clearDataSource() {
    22.         dataSources.remove();
    23.     }
    24. }

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

    2. 创建一个类继承AbstractRoutingDataSource

    1. import org.apache.logging.log4j.LogManager;
    2. import org.apache.logging.log4j.Logger;
    3. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    4.  
    5. public class RoutingDataSource extends AbstractRoutingDataSource {
    6.     private Logger logger = LogManager.getLogger();
    7.  
    8.     @Override
    9.     protected Object determineCurrentLookupKey() {
    10.         String dataSource = RoutingDataSourceHolder.getDataSource();
    11.         logger.info("使用数据源:{}", dataSource);
    12.         return dataSource;
    13.     }
    14. }

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

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

    3. 配置主从数据库类DataSourceConfigurer

    1. DataSourceConfigurer

    1. import com.alibaba.druid.pool.DruidDataSource;
    2. import com.custom.common.utils.StringUtils;
    3. import org.apache.logging.log4j.LogManager;
    4. import org.apache.logging.log4j.Logger;
    5. import org.springframework.beans.factory.annotation.Qualifier;
    6. import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
    7. import org.springframework.boot.context.properties.ConfigurationProperties;
    8. import org.springframework.context.annotation.Bean;
    9. import org.springframework.context.annotation.Configuration;
    10. import org.springframework.context.annotation.DependsOn;
    11. import org.springframework.context.annotation.Primary;
    12.  
    13. import javax.sql.DataSource;
    14. import java.util.HashMap;
    15. import java.util.Map;
    16.  
    17. /**
    18.  * 配置主从数据库
    19.  */
    20. @Configuration
    21. public class DataSourceConfigurer {
    22.     private Logger logger = LogManager.getLogger();
    23.  
    24.     public final static String MASTER_DATASOURCE = "masterDataSource";
    25.     public final static String SLAVE_DATASOURCE = "slaveDataSource";
    26.  
    27.     @Bean(MASTER_DATASOURCE)
    28.     @ConfigurationProperties(prefix = "spring.datasource")
    29.     public DruidDataSource masterDataSource(DataSourceProperties properties) {
    30.         DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
    31.         logger.info("配置主数据库:{}", build);
    32.         return build;
    33.     }
    34.  
    35.     @Bean(SLAVE_DATASOURCE)
    36.     @ConfigurationProperties(prefix = "spring.slave-datasource")
    37.     public DruidDataSource slaveDataSource(DataSourceProperties properties) {
    38.         DruidDataSource build = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
    39.         logger.info("配置从数据库:{}", build);
    40.         return build;
    41.     }
    42.  
    43.     /**
    44.      * Primary 优先使用该Bean
    45.      * DependsOn 先执行主从数据库的配置
    46.      * Qualifier 指定使用哪个Bean
    47.      *
    48.      * @param masterDataSource
    49.      * @param slaveDataSource
    50.      * @return
    51.      */
    52.     @Bean
    53.     @Primary
    54.     @DependsOn(value = {MASTER_DATASOURCE, SLAVE_DATASOURCE})
    55.     public DataSource routingDataSource(@Qualifier(MASTER_DATASOURCE) DruidDataSource masterDataSource,
    56.                                         @Qualifier(SLAVE_DATASOURCE) DruidDataSource slaveDataSource) {
    57.         if (StringUtils.isBlank(slaveDataSource.getUrl())) {
    58.             logger.info("没有配置从数据库,默认使用主数据库");
    59.             return masterDataSource;
    60.         }
    61.         Map map = new HashMap<>();
    62.         map.put(DataSourceConfigurer.MASTER_DATASOURCE, masterDataSource);
    63.         map.put(DataSourceConfigurer.SLAVE_DATASOURCE, slaveDataSource);
    64.         RoutingDataSource routing = new RoutingDataSource();
    65.         //设置动态数据源
    66.         routing.setTargetDataSources(map);
    67.         //设置默认数据源
    68.         routing.setDefaultTargetDataSource(masterDataSource);
    69.         logger.info("主从数据库配置完成");
    70.         return routing;
    71.     }
    72. }

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

    Map 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

    1. # ----------------------------------------
    2. # 主数据库
    3. # ----------------------------------------
    4. spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    5. spring.datasource.url=jdbc:mysql://127.0.0.1:3306/custom?useunicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
    6. spring.datasource.username=root
    7. spring.datasource.password=root
    8. spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
    9.  
    10. # ----------------------------------------
    11. # 从数据库
    12. # ----------------------------------------
    13. spring.slave-datasource.type=com.alibaba.druid.pool.DruidDataSource
    14. spring.slave-datasource.url=jdbc:mysql://127.0.0.1:3309/custom?useunicode=true&characterEncoding=utf8&serverTimezone=GMT%2b8
    15. spring.slave-datasource.username=root
    16. spring.slave-datasource.password=root
    17. spring.slave-datasource.driver-class-name=com.mysql.cj.jdbc.Driver

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

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

    4. 创建aop注解和类

    1. DataSourceWith

    1. import java.lang.annotation.*;
    2.  
    3. @Retention(RetentionPolicy.RUNTIME)
    4. @Target({ElementType.TYPE, ElementType.METHOD})
    5. @Documented
    6. public @interface DataSourceWith {
    7.     String key() default "";
    8. }

    2.DataSourceWithAspect

    1. import org.aspectj.lang.JoinPoint;
    2. import org.aspectj.lang.annotation.*;
    3. import org.aspectj.lang.reflect.MethodSignature;
    4. import org.springframework.core.annotation.Order;
    5. import org.springframework.stereotype.Component;
    6.  
    7. import java.lang.reflect.Method;
    8.  
    9. @Aspect
    10. @Order(-1)// 保证该AOP在@Transactional之前运行
    11. @Component
    12. public class DataSourceWithAspect {
    13.  
    14.     /**
    15.      * 使用DataSourceWith注解就拦截
    16.      */
    17.     @Pointcut("@annotation(com.custom.configure.datasource.DataSourceWith)||@within(com.custom.configure.datasource.DataSourceWith)")
    18.     public void doPointcut() {
    19.  
    20.     }
    21.  
    22.     /**
    23.      * 方法前,为了在事务前设置
    24.      */
    25.     @Before("doPointcut()")
    26.     public void doBefore(JoinPoint joinPoint) {
    27.         MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
    28.         Method method = methodSignature.getMethod();
    29.         // 获取注解对象
    30.         DataSourceWith dataSource = method.getAnnotation(DataSourceWith.class);
    31.         if (dataSource == null) {
    32.             //方法没有就获取类上的
    33.             dataSource = method.getDeclaringClass().getAnnotation(DataSourceWith.class);
    34.         }
    35.         String key = dataSource.key();
    36.         RoutingDataSourceHolder.setDataSource(key);
    37.     }
    38.  
    39.     @After("doPointcut()")
    40.     public void doAfter(JoinPoint joinPoint) {
    41.         RoutingDataSourceHolder.clearDataSource();
    42.     }
    43.  
    44. }

    3.使用和效果

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

    1. /**
    2.      * 获取部门列表
    3.      **/
    4.     @DataSourceWith(key = DataSourceConfigurer.SLAVE_DATASOURCE)
    5.     public List findDeptTree() {
    6.         logger.debug("获取部门树数据");
    7.         List deptList = new ArrayList<>();
    8.         return deptList;
    9.     }

    结果:动态切换成功

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

  • 相关阅读:
    Uniapp 酷炫钱包页面模板 直接引用
    简单对比一下 C 与 Go 两种语言
    linux同步之原子操作(二)
    CPP语法(六)——函数模板
    Uniapp零基础开发学习笔记(4) -顶部导航栏titleNView的制作
    python爬虫入门学习
    docker安装mysql
    【C语言 模拟实现strcpy函数】
    【Jmeter 简单使用】
    【Spring】从Spring源码入手分析广播与监听并完成项目实战
  • 原文地址:https://blog.csdn.net/weixin_52850476/article/details/126772218