当前位置:   article > 正文

在Spring Boot项目中通过自定义注解实现多数据源以及主备数据库切换_springboot自带的切换数据源的注解是啥

springboot自带的切换数据源的注解是啥

在现代的企业应用开发中,使用多数据源是一个常见的需求。尤其在关键应用中,设置主备数据库可以提高系统的可靠性和可用性。在这篇博客中,我将展示如何在Spring Boot项目中通过自定义注解实现多数据源以及主备数据库切换。

在此说明

我这里以dm6、dm7来举例多数据源 ,以两个dm6来举例主备数据库,基本大部分数据库都通用,举一反三即可。

对于dm6不熟悉但是又要用的可以看我这篇博客

Spring Boot项目中使用MyBatis连接达梦数据库6

1. 环境依赖

首先,确保你的Spring Boot项目中已经添加了以下依赖:

  1. <!-- Lombok依赖,用于简化Java代码 -->
  2. <dependency>
  3. <groupId>org.projectlombok</groupId>
  4. <artifactId>lombok</artifactId>
  5. </dependency>
  6. <!-- MyBatis Spring Boot Starter依赖,用于集成MyBatis和Spring Boot -->
  7. <!-- 注意:这里使用1.3.0版本,因为DM6不支持1.3以上版本 -->
  8. <dependency>
  9. <groupId>org.mybatis.spring.boot</groupId>
  10. <artifactId>mybatis-spring-boot-starter</artifactId>
  11. <version>1.3.0</version>
  12. </dependency>
  13. <!-- Spring Boot Starter AOP依赖,用于实现AOP功能 -->
  14. <dependency>
  15. <groupId>org.springframework.boot</groupId>
  16. <artifactId>spring-boot-starter-aop</artifactId>
  17. </dependency>
  18. <!-- DM6 JDBC驱动,用于连接DM6数据库 -->
  19. <dependency>
  20. <groupId>com.github.tianjing</groupId>
  21. <artifactId>Dm6JdbcDriver</artifactId>
  22. <version>1.0.0</version>
  23. </dependency>
  24. <!-- DM8 JDBC驱动,用于连接DM7/DM8数据库 -->
  25. <dependency>
  26. <groupId>com.dameng</groupId>
  27. <artifactId>DmJdbcDriver18</artifactId>
  28. <version>8.1.3.62</version>
  29. </dependency>
  30. <!-- Hutool工具类库,用于简化Java开发 -->
  31. <dependency>
  32. <groupId>cn.hutool</groupId>
  33. <artifactId>hutool-all</artifactId>
  34. <version>5.8.27</version>
  35. </dependency>

2. 配置文件

  1. spring.datasource:
  2. dmprimary:
  3. driver-class-name: dm6.jdbc.driver.DmDriver # 驱动类名称,用于连接 DM6 数据库
  4. jdbc-url: jdbc:dm6://localhost:12345/xxxx # JDBC URL,指定 DM6 数据库的地址和端口
  5. username: xxxx # 数据库用户名
  6. password: xxxxxxx # 数据库密码
  7. connection-test-query: select 1 # 用于测试数据库连接的查询语句
  8. type: com.zaxxer.hikari.HikariDataSource # 使用 HikariCP 作为连接池实现
  9. maximum-pool-size: 8 # 最大连接池大小
  10. minimum-idle: 2 # 最小空闲连接数
  11. idle-timeout: 600000 # 空闲连接的超时时间,单位毫秒
  12. max-lifetime: 1800000 # 连接的最大生命周期,单位毫秒
  13. connection-timeout: 3000 # 获取连接的超时时间,单位毫秒
  14. validation-timeout: 3000 # 验证连接的超时时间,单位毫秒
  15. initialization-fail-timeout: 1 # 初始化失败时的超时时间,单位毫秒
  16. leak-detection-threshold: 0 # 连接泄漏检测的阈值,单位毫秒
  17. dmbackup:
  18. driver-class-name: dm6.jdbc.driver.DmDriver
  19. jdbc-url: jdbc:dm6://8.8.8.8:12345/xxxx
  20. username: xxxxxxx
  21. password: xxxxx
  22. connection-test-query: select 1
  23. type: com.zaxxer.hikari.HikariDataSource
  24. maximum-pool-size: 8
  25. minimum-idle: 2
  26. idle-timeout: 600000
  27. max-lifetime: 1800000
  28. connection-timeout: 30000
  29. validation-timeout: 5000
  30. initialization-fail-timeout: 1
  31. leak-detection-threshold: 0
  32. dm7:
  33. driver-class-name: dm.jdbc.driver.DmDriver
  34. jdbc-url: jdbc:dm://localhost:5236/xxxx
  35. password: xxxxxxxxx
  36. username: xxxxxx
  37. connection-test-query: select 1
  38. type: com.zaxxer.hikari.HikariDataSource
  39. maximum-pool-size: 10
  40. minimum-idle: 2
  41. idle-timeout: 600000
  42. max-lifetime: 1800000
  43. connection-timeout: 30000
  44. validation-timeout: 5000
  45. initialization-fail-timeout: 1
  46. leak-detection-threshold: 0
  47. mybatis:
  48. mapper-locations: classpath:/mappers/*.xml # 修改为你的 MyBatis XML 映射文件路径
  49. configuration:
  50. # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  51. map-underscore-to-camel-case: true

3. 定义数据源相关的常量

  1. /**
  2. * 定义数据源相关的常量
  3. * @Author: 阿水
  4. * @Date: 2024-05-24
  5. */
  6. public interface DataSourceConstant {
  7. String DB_NAME_DM6 = "dm";
  8. String DB_NAME_DM6_BACKUP = "dmBackup";
  9. String DB_NAME_DM7 = "dm7";
  10. }

4. 创建自定义注解

 
  1. import java.lang.annotation.*;
  2. /**
  3. * 数据源切换注解
  4. * @Author: 阿水
  5. * @Date: 2024-05-24
  6. */
  7. @Target({ElementType.METHOD, ElementType.TYPE})
  8. @Retention(RetentionPolicy.RUNTIME)
  9. @Documented
  10. @Inherited
  11. public @interface DataSource {
  12. String value() default DataSourceConstant.DB_NAME_DM6;
  13. }

5. 动态数据源类

  1. /**
  2. * 动态数据源类
  3. * @Author: 阿水
  4. * @Date: 2024-05-24
  5. */
  6. public class DynamicDataSource extends AbstractRoutingDataSource {
  7. @Override
  8. protected Object determineCurrentLookupKey() {
  9. return DataSourceUtil.getDB();
  10. }
  11. }

动态数据源切换的核心实现

在多数据源配置中,我们需要一个类来动态决定当前使用的数据源,这就是 DynamicDataSource 类。它继承自 Spring 提供的 AbstractRoutingDataSource,通过覆盖 determineCurrentLookupKey 方法,从 ThreadLocal 中获取当前数据源的标识符,并返回该标识符以决定要使用的数据源。

6. 数据源工具类

  1. /**
  2. * 数据源工具类
  3. * @Author: 阿水
  4. * @Date: 2024-05-24
  5. */
  6. public class DataSourceUtil {
  7. /**
  8. * 数据源属于一个公共的资源
  9. * 采用ThreadLocal可以保证在多线程情况下线程隔离
  10. */
  11. private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
  12. /**
  13. * 设置数据源名
  14. * @param dbType
  15. */
  16. public static void setDB(String dbType) {
  17. contextHolder.set(dbType);
  18. }
  19. /**
  20. * 获取数据源名
  21. * @return
  22. */
  23. public static String getDB() {
  24. return (contextHolder.get());
  25. }
  26. /**
  27. * 清除数据源名
  28. */
  29. public static void clearDB() {
  30. contextHolder.remove();
  31. }
  32. }

7. 数据源配置类

  1. /**
  2. * 数据源配置类,用于配置多个数据源,并设置动态数据源。
  3. * @Author: 阿水
  4. * @Date: 2024-05-24
  5. */
  6. @Configuration
  7. public class DataSourceConfig {
  8. @Bean(name = "primaryDataSource")
  9. @ConfigurationProperties(prefix = "spring.datasource.dmprimary")
  10. public DataSource primaryDataSource() {
  11. return DataSourceBuilder.create().build();
  12. }
  13. @Bean(name = "backupDataSource")
  14. @ConfigurationProperties(prefix = "spring.datasource.dmbackup")
  15. public DataSource backupDataSource() {
  16. return DataSourceBuilder.create().build();
  17. }
  18. @Bean(name = "dm7")
  19. @ConfigurationProperties(prefix = "spring.datasource.dm7")
  20. public DataSource dataSourceDm7() {
  21. return DataSourceBuilder.create().build();
  22. }
  23. /**
  24. * 配置动态数据源,将多个数据源加入到动态数据源中
  25. * 设置 primaryDataSource 为默认数据源
  26. */
  27. @Primary
  28. @Bean(name = "dynamicDataSource")
  29. public DataSource dynamicDataSource() {
  30. DynamicDataSource dynamicDataSource = new DynamicDataSource();
  31. dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
  32. Map<Object, Object> dsMap = new HashMap<>();
  33. dsMap.put(DataSourceConstant.DB_NAME_DM6, primaryDataSource());
  34. dsMap.put(DataSourceConstant.DB_NAME_DM6_BACKUP, backupDataSource());
  35. dsMap.put(DataSourceConstant.DB_NAME_DM7, dataSourceDm7());
  36. dynamicDataSource.setTargetDataSources(dsMap);
  37. return dynamicDataSource;
  38. }
  39. /**
  40. * 配置事务管理器,使用动态数据源
  41. */
  42. @Bean
  43. public PlatformTransactionManager transactionManager() {
  44. return new DataSourceTransactionManager(dynamicDataSource());
  45. }
  46. }

8. 数据源切换器

 
  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
  4. import javax.annotation.PostConstruct;
  5. import javax.sql.DataSource;
  6. import java.sql.Connection;
  7. import java.sql.SQLException;
  8. import java.util.HashMap;
  9. import java.util.Map;
  10. /**
  11. * 数据源切换器
  12. * @Author: 阿水
  13. * @Date: 2024-05-24
  14. */
  15. @Configuration
  16. public class DataSourceSwitcher extends AbstractRoutingDataSource {
  17. @Autowired
  18. private DataSource primaryDataSource;
  19. @Autowired
  20. private DataSource backupDataSource;
  21. private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
  22. @PostConstruct
  23. public void init() {
  24. this.setDefaultTargetDataSource(primaryDataSource);
  25. Map<Object, Object> dataSourceMap = new HashMap<>();
  26. dataSourceMap.put("primary", primaryDataSource);
  27. dataSourceMap.put("backup", backupDataSource);
  28. this.setTargetDataSources(dataSourceMap);
  29. this.afterPropertiesSet();
  30. }
  31. @Override
  32. protected Object determineCurrentLookupKey() {
  33. return CONTEXT_HOLDER.get();
  34. }
  35. public static void setDataSource(String dataSource) {
  36. CONTEXT_HOLDER.set(dataSource);
  37. }
  38. public static void clearDataSource() {
  39. CONTEXT_HOLDER.remove();
  40. }
  41. public boolean isPrimaryDataSourceAvailable() {
  42. return isDataSourceAvailable(primaryDataSource);
  43. }
  44. public boolean isBackupDataSourceAvailable() {
  45. return isDataSourceAvailable(backupDataSource);
  46. }
  47. private boolean isDataSourceAvailable(DataSource dataSource) {
  48. try (Connection connection = dataSource.getConnection()) {
  49. return true;
  50. } catch (RuntimeException | SQLException e) {
  51. return false;
  52. }
  53. }
  54. }

这个类通过继承 AbstractRoutingDataSource 实现了动态数据源切换的功能。它使用 ThreadLocal 变量实现线程隔离的数据源标识存储,并提供了设置和清除当前数据源的方法。在 Bean 初始化时,它将主数据源设为默认数据源,并将主数据源和备用数据源添加到数据源映射中。该类还提供了检查数据源可用性的方法,通过尝试获取连接来判断数据源是否可用。

这个类是实现动态数据源切换的核心部分,配合 Spring AOP 可以实现基于注解的数据源切换逻辑,从而实现多数据源和主备数据库的切换功能。

9. AOP切面类

  1. import cn.hutool.core.util.ObjUtil;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.annotation.Around;
  5. import org.aspectj.lang.annotation.Aspect;
  6. import org.aspectj.lang.annotation.Pointcut;
  7. import org.aspectj.lang.reflect.MethodSignature;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.context.annotation.EnableAspectJAutoProxy;
  10. import org.springframework.core.annotation.AnnotationUtils;
  11. import org.springframework.stereotype.Component;
  12. import java.util.Objects;
  13. /**
  14. * AOP切面
  15. * @Author: 阿水
  16. * @Date: 2024-05-24
  17. */
  18. @Aspect
  19. @Component
  20. @Slf4j
  21. @EnableAspectJAutoProxy
  22. public class DataSourceAspect {
  23. @Autowired
  24. private DataSourceSwitcher dataSourceSwitcher;
  25. @Autowired
  26. private TimeCacheConfig cacheConfig;
  27. @Pointcut("@annotation(com.lps.config.DataSource) || @within(com.lps.config.DataSource)")
  28. public void dataSourcePointCut() {
  29. }
  30. /**
  31. * AOP环绕通知,拦截标注有@DataSource注解的方法或类
  32. * @param point 连接点信息
  33. * @return 方法执行结果
  34. * @throws Throwable 异常信息
  35. */
  36. @Around("dataSourcePointCut()")
  37. public Object around(ProceedingJoinPoint point) throws Throwable {
  38. // 获取需要切换的数据源
  39. DataSource dataSource = getDataSource(point);
  40. log.info("初始数据源为{}", dataSource != null ? dataSource.value() : "默认数据源");
  41. // 设置数据源
  42. if (dataSource != null) {
  43. DataSourceUtil.setDB(dataSource.value());
  44. }
  45. // 处理主数据源逻辑
  46. if (DataSourceUtil.getDB().equals(DataSourceConstant.DB_NAME_DM6)) {
  47. handlePrimaryDataSource();
  48. }
  49. // 获取当前数据源
  50. String currentDataSource = DataSourceUtil.getDB();
  51. log.info("最终数据源为{}", currentDataSource);
  52. try {
  53. // 执行被拦截的方法
  54. return point.proceed();
  55. } finally {
  56. // 清除数据源
  57. DataSourceUtil.clearDB();
  58. log.info("清除数据源");
  59. }
  60. }
  61. /**
  62. * 处理主数据库的数据源切换逻辑
  63. */
  64. private void handlePrimaryDataSource() {
  65. // 检查缓存中是否有主数据库挂掉的标记
  66. if (ObjUtil.isNotEmpty(cacheConfig.timeCacheHc().get("dataSource", false))) {
  67. // 切换到备用数据源
  68. DataSourceUtil.setDB(DataSourceConstant.DB_NAME_DM6_BACKUP);
  69. log.info("切换到备用数据源");
  70. } else {
  71. // 检查主数据库状态并切换数据源
  72. checkAndSwitchDataSource();
  73. }
  74. }
  75. /**
  76. * 检查主数据库状态并在必要时切换到备用数据库
  77. */
  78. private void checkAndSwitchDataSource() {
  79. try {
  80. // 检查主数据库是否可用
  81. if (dataSourceSwitcher.isPrimaryDataSourceAvailable()) {
  82. log.info("主数据源没有问题,一切正常");
  83. } else {
  84. // 主数据库不可用,更新缓存并切换到备用数据源
  85. cacheConfig.timeCacheHc().put("dataSource", "主数据库挂了,boom");
  86. log.info("主数据源存在问题,切换备用数据源");
  87. DataSourceUtil.setDB(DataSourceConstant.DB_NAME_DM6_BACKUP);
  88. }
  89. } catch (Exception e) {
  90. // 主数据库和备用数据库都不可用,抛出异常
  91. throw new RuntimeException("两个数据库都有问题 GG", e);
  92. }
  93. }
  94. /**
  95. * 获取需要切换的数据源
  96. * @param point 连接点信息
  97. * @return 数据源注解信息
  98. */
  99. private DataSource getDataSource(ProceedingJoinPoint point) {
  100. MethodSignature signature = (MethodSignature) point.getSignature();
  101. DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
  102. if (Objects.nonNull(dataSource)) {
  103. return dataSource;
  104. }
  105. return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
  106. }
  107. }

10. 缓存配置类

  1. /**
  2. * 缓存配置类
  3. * @Author: 阿水
  4. * @Date: 2024-05-24
  5. */
  6. @Configuration
  7. public class TimeCacheConfig {
  8. @Bean
  9. public TimedCache timeCacheHc() {
  10. return CacheUtil.newTimedCache(5 * 60 * 1000);
  11. }
  12. }

定时缓存,对被缓存的对象定义一个过期时间,当对象超过过期时间会被清理。此缓存没有容量限制,对象只有在过期后才会被移除,详情可以翻阅hutool官方文档

超时-TimedCache

11. 运行结果:

接口方法上使用注解即可,举例:我这里每个方法都被注解 @DataSource 修饰

  1. /**
  2. * @author 19449
  3. * @description 针对表【STUDENT】的数据库操作Mapper
  4. * @createDate 2024-03-22 22:09:34
  5. * @Entity com.lps.domain.Student
  6. */
  7. @Mapper
  8. //@DataSource()
  9. public interface StudentMapper {
  10. @DataSource()
  11. Integer selectCountDm6();
  12. @DataSource(DataSourceConstant.DB_NAME_DM6_BACKUP)
  13. Integer selectCountDm6Backup();
  14. @DataSource(DataSourceConstant.DB_NAME_DM7)
  15. Integer selectCountDm7();
  16. @SuppressWarnings("MybatisXMapperMethodInspection")
  17. @DataSource()
  18. List<Student> selectList();
  19. @DataSource()
  20. void insert(Student record);
  21. }

我dmprimary的信息随便写的,可以发现可以自动切换到备用数据库。 可以看到数据源切换毫无问题,主备数据源切换 毫无问题。

12. 结论

通过以上步骤,本次在Spring Boot项目中实现了自定义注解来管理多数据源,并且在主数据库不可用时自动切换到备用数据库。为了提升效率,我们还使用了缓存来记住主数据库的状态,避免频繁的数据库状态检查。这种设计不仅提高了系统的可靠性和可维护性,还能保证在关键时刻系统能够稳定运行。

希望这篇博客能对你有所帮助,如果你有任何问题或建议,欢迎留言讨论。(有问题可以私聊看到就会回)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/845791
推荐阅读
相关标签
  

闽ICP备14008679号