• SpringBoot 实现 Oracle 主从数据库的动态切换,并实现读写分离


    1、问题提出

    目前 Oracle 中有两个数据库,要实现一个数据库只进行读操作,另一个数据库进行写操作,也即数据库的读写分离,该怎么做?

    2、简要说明:本问题与【主从复制、读写分离】还不太一样

    主从复制、读写分离一般是一起使用的。目的很简单,就是为了提高数据库的并发性能。你想,假设是单机,读写都在一台 MySQL 上面完成,性能肯定不高。如果有三台MySQL,一台 mater 只负责写操作,两台 salve 只负责读操作,性能不就能大大提高了吗?
    所以主从复制、读写分离就是为了数据库能支持更大的并发。
    随着业务量的扩展,如果是单机部署的 MySQL,会导致I/O频率过高。采用主从复制、读写分离可以提高数据库的可用性。
    而本问题只需要实现主从数据库的动态切换即可。

    3、AbstractRoutingDataSource

    SpringBoot 提供了 AbstractRoutingDataSource,可以根据用户自定义的规则选择当前的数据源,这样我们每次访问数据库之前,设置要使用的数据源,就可以实现数据源的动态切换。

    4、具体实现

    1、创建一个类继承 AbstractRoutingDataSource

    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * 1. 创建 RoutingDataSource 继承 AbstractRoutingDataSource
     * 重写 determineCurrentLookupKey方法,返回要使用的数据源key值。
     */
    public class RoutingDataSource extends AbstractRoutingDataSource {
        private Logger logger = LogManager.getLogger();
    
        @Override
        protected Object determineCurrentLookupKey() {
            String dataSource = RoutingDataSourceHolder.getDataSource();
            logger.info("使用数据源:{}", dataSource);
            return dataSource;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    2、创建一个管理数据源 key 值的类,RoutingDataSourceManager
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    
    /**
     * 2. 创建一个管理数据源key值的类 RoutingDataSourceHolder
     * 代码设置了一个事务内使用同一个数据源。
     */
    public class RoutingDataSourceManager {
    
        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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    3、application.properties

    # OracleDbProperties  
    # master dbsource
    spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
    spring.datasource.url=jdbc:oracle:thin:@xxx.xxx.xxx.7:1521:xxx
    spring.datasource.username=xxx
    spring.datasource.password=xxx
    
    # slave dbsource
    spring.slave-datasource.type=com.alibaba.druid.pool.DruidDataSource
    spring.slave-datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
    spring.slave-datasource.url=jdbc:oracle:thin:@xxx.xxx.xxx.6:1521:xxx
    spring.slave-datasource.username=xxx
    spring.slave-datasource.password=xxx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    4、配置主从数据库

    import com.alibaba.druid.pool.DruidDataSource;
    import org.apache.commons.lang3.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;
    
    /**
     * 配置主从数据库:主:xxx.xxx.xxx.7;从:xxx.xxx.xxx.6
     */
    @Configuration
    public class DataSourceConfigurer {
        private Logger logger = LogManager.getLogger();
    
        public final static String MASTER_DATASOURCE = "masterDataSource";// 主数据库
        public final static String SLAVE_DATASOURCE = "slaveDataSource";// 从数据库
    
        // 主数据库:.7
        @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;
        }
    
        // 从数据库:.6
        @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;
            }
    
            // 设置初始化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);
            logger.info("主从数据库配置完成");
            return routing;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    5、自定义注解和切面
    自定义注解:

    import java.lang.annotation.*;
    
    @Retention(RetentionPolicy.RUNTIME)// 声明注解有效的时间
    @Target({ElementType.TYPE, ElementType.METHOD})// 说明该注解可以写在类和方法上
    @Documented
    public @interface DataSourceWith {
        String key() default "";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    切面类:

    
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-aopartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    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.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(cn.edu.zzuli.hnsmz.annotation.DataSourceWith)||@within(cn.edu.zzuli.hnsmz.annotation.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();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    6、使用
    @DataSourceWith(key = DataSourceConfigurer.SLAVE_DATASOURCE)
    public Long selectById(String id) {
        return studentService.selectById(id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参考自:https://blog.csdn.net/m0_68615056/article/details/123738282

  • 相关阅读:
    RK3568驱动指南|第六篇-平台总线-第53章 probe函数编写实验
    【Springcloud微服务】MybatisPlus下篇
    一名合格的软件测试工程师,应该具备哪些技术能力?
    不同类型的软件测试
    springboot+vue美容院美妆化妆品商城管理系统nodejs
    Spring Boot | Spring Boot 实现 “Redis缓存管理“
    百趣代谢组学资讯:植物挥发性有机物生物合成机理及抗菌性研究IF14.46
    洛谷P1242 新汉诺塔
    虹科方案 | LIN/CAN总线汽车零部件测试方案
    关于go中资源泄漏/goroutine泄漏/内存泄漏/CPU打满等情况分析
  • 原文地址:https://blog.csdn.net/weixin_45410366/article/details/126685255