1、问题提出
    目前 Oracle 中有两个数据库,要实现一个数据库只进行读操作,另一个数据库进行写操作,也即数据库的主从复制,该怎么做?
    2、简要说明:为什么使用主从复制、读写分离?
    主从复制、读写分离一般是一起使用的。目的很简单,就是为了提高数据库的并发性能。你想,假设是单机,读写都在一台 MySQL 上面完成,性能肯定不高。如果有三台MySQL,一台 mater 只负责写操作,两台 salve 只负责读操作,性能不就能大大提高了吗?
    所以主从复制、读写分离就是为了数据库能支持更大的并发。
    随着业务量的扩展,如果是单机部署的 MySQL,会导致I/O频率过高。采用主从复制、读写分离可以提高数据库的可用性。
    3、AbstractRoutingDataSource
    SpringBoot 提供了 AbstractRoutingDataSource,可以根据用户自定义的规则选择当前的数据源,这样我们每次访问数据库之前,设置要使用的数据源,就可以实现数据源的动态切换。

    4、具体实现
    1、创建一个类继承 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. * 1. 创建 RoutingDataSource 继承 AbstractRoutingDataSource
    6. * 重写 determineCurrentLookupKey方法,返回要使用的数据源key值。
    7. */
    8. public class RoutingDataSource extends AbstractRoutingDataSource {
    9. private Logger logger = LogManager.getLogger();
    10. @Override
    11. protected Object determineCurrentLookupKey() {
    12. String dataSource = RoutingDataSourceHolder.getDataSource();
    13. logger.info("使用数据源:{}", dataSource);
    14. return dataSource;
    15. }
    16. }

    2、创建一个管理数据源 key 值的类,RoutingDataSourceManager

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

    3、application.properties

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

    4、配置主从数据库

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

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

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

    切面类:

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

    6、使用

    1. @DataSourceWith(key = DataSourceConfigurer.SLAVE_DATASOURCE)
    2. public Long selectById(String id) {
    3. return studentService.selectById(id);
    4. }