赞
踩
在此说明:
我这里以dm6、dm7来举例多数据源 ,以两个dm6来举例主备数据库,基本大部分数据库都通用,举一反三即可。
对于dm6不熟悉但是又要用的可以看我这篇博客
Spring Boot项目中使用MyBatis连接达梦数据库6
首先,确保你的Spring Boot项目中已经添加了以下依赖:
- <!-- Lombok依赖,用于简化Java代码 -->
- <dependency>
- <groupId>org.projectlombok</groupId>
- <artifactId>lombok</artifactId>
- </dependency>
-
- <!-- MyBatis Spring Boot Starter依赖,用于集成MyBatis和Spring Boot -->
- <!-- 注意:这里使用1.3.0版本,因为DM6不支持1.3以上版本 -->
- <dependency>
- <groupId>org.mybatis.spring.boot</groupId>
- <artifactId>mybatis-spring-boot-starter</artifactId>
- <version>1.3.0</version>
- </dependency>
-
- <!-- Spring Boot Starter AOP依赖,用于实现AOP功能 -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-aop</artifactId>
- </dependency>
-
- <!-- DM6 JDBC驱动,用于连接DM6数据库 -->
- <dependency>
- <groupId>com.github.tianjing</groupId>
- <artifactId>Dm6JdbcDriver</artifactId>
- <version>1.0.0</version>
- </dependency>
-
- <!-- DM8 JDBC驱动,用于连接DM7/DM8数据库 -->
- <dependency>
- <groupId>com.dameng</groupId>
- <artifactId>DmJdbcDriver18</artifactId>
- <version>8.1.3.62</version>
- </dependency>
-
- <!-- Hutool工具类库,用于简化Java开发 -->
- <dependency>
- <groupId>cn.hutool</groupId>
- <artifactId>hutool-all</artifactId>
- <version>5.8.27</version>
- </dependency>

spring.datasource: dmprimary: driver-class-name: dm6.jdbc.driver.DmDriver # 驱动类名称,用于连接 DM6 数据库 jdbc-url: jdbc:dm6://localhost:12345/xxxx # JDBC URL,指定 DM6 数据库的地址和端口 username: xxxx # 数据库用户名 password: xxxxxxx # 数据库密码 connection-test-query: select 1 # 用于测试数据库连接的查询语句 type: com.zaxxer.hikari.HikariDataSource # 使用 HikariCP 作为连接池实现 maximum-pool-size: 8 # 最大连接池大小 minimum-idle: 2 # 最小空闲连接数 idle-timeout: 600000 # 空闲连接的超时时间,单位毫秒 max-lifetime: 1800000 # 连接的最大生命周期,单位毫秒 connection-timeout: 3000 # 获取连接的超时时间,单位毫秒 validation-timeout: 3000 # 验证连接的超时时间,单位毫秒 initialization-fail-timeout: 1 # 初始化失败时的超时时间,单位毫秒 leak-detection-threshold: 0 # 连接泄漏检测的阈值,单位毫秒 dmbackup: driver-class-name: dm6.jdbc.driver.DmDriver jdbc-url: jdbc:dm6://8.8.8.8:12345/xxxx username: xxxxxxx password: xxxxx connection-test-query: select 1 type: com.zaxxer.hikari.HikariDataSource maximum-pool-size: 8 minimum-idle: 2 idle-timeout: 600000 max-lifetime: 1800000 connection-timeout: 30000 validation-timeout: 5000 initialization-fail-timeout: 1 leak-detection-threshold: 0 dm7: driver-class-name: dm.jdbc.driver.DmDriver jdbc-url: jdbc:dm://localhost:5236/xxxx password: xxxxxxxxx username: xxxxxx connection-test-query: select 1 type: com.zaxxer.hikari.HikariDataSource maximum-pool-size: 10 minimum-idle: 2 idle-timeout: 600000 max-lifetime: 1800000 connection-timeout: 30000 validation-timeout: 5000 initialization-fail-timeout: 1 leak-detection-threshold: 0 mybatis: mapper-locations: classpath:/mappers/*.xml # 修改为你的 MyBatis XML 映射文件路径 configuration: # log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true
- /**
- * 定义数据源相关的常量
- * @Author: 阿水
- * @Date: 2024-05-24
- */
- public interface DataSourceConstant {
- String DB_NAME_DM6 = "dm";
- String DB_NAME_DM6_BACKUP = "dmBackup";
- String DB_NAME_DM7 = "dm7";
- }
- import java.lang.annotation.*;
- /**
- * 数据源切换注解
- * @Author: 阿水
- * @Date: 2024-05-24
- */
- @Target({ElementType.METHOD, ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Inherited
- public @interface DataSource {
-
- String value() default DataSourceConstant.DB_NAME_DM6;
-
- }
- /**
- * 动态数据源类
- * @Author: 阿水
- * @Date: 2024-05-24
- */
- public class DynamicDataSource extends AbstractRoutingDataSource {
- @Override
- protected Object determineCurrentLookupKey() {
- return DataSourceUtil.getDB();
- }
- }
动态数据源切换的核心实现
在多数据源配置中,我们需要一个类来动态决定当前使用的数据源,这就是 DynamicDataSource
类。它继承自 Spring 提供的 AbstractRoutingDataSource
,通过覆盖 determineCurrentLookupKey
方法,从 ThreadLocal
中获取当前数据源的标识符,并返回该标识符以决定要使用的数据源。
- /**
- * 数据源工具类
- * @Author: 阿水
- * @Date: 2024-05-24
- */
- public class DataSourceUtil {
- /**
- * 数据源属于一个公共的资源
- * 采用ThreadLocal可以保证在多线程情况下线程隔离
- */
- private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
-
- /**
- * 设置数据源名
- * @param dbType
- */
- public static void setDB(String dbType) {
- contextHolder.set(dbType);
- }
-
- /**
- * 获取数据源名
- * @return
- */
- public static String getDB() {
- return (contextHolder.get());
- }
-
- /**
- * 清除数据源名
- */
- public static void clearDB() {
- contextHolder.remove();
- }
- }

-
- /**
- * 数据源配置类,用于配置多个数据源,并设置动态数据源。
- * @Author: 阿水
- * @Date: 2024-05-24
- */
- @Configuration
- public class DataSourceConfig {
-
- @Bean(name = "primaryDataSource")
- @ConfigurationProperties(prefix = "spring.datasource.dmprimary")
- public DataSource primaryDataSource() {
- return DataSourceBuilder.create().build();
- }
-
- @Bean(name = "backupDataSource")
- @ConfigurationProperties(prefix = "spring.datasource.dmbackup")
- public DataSource backupDataSource() {
- return DataSourceBuilder.create().build();
- }
-
- @Bean(name = "dm7")
- @ConfigurationProperties(prefix = "spring.datasource.dm7")
- public DataSource dataSourceDm7() {
- return DataSourceBuilder.create().build();
- }
- /**
- * 配置动态数据源,将多个数据源加入到动态数据源中
- * 设置 primaryDataSource 为默认数据源
- */
- @Primary
- @Bean(name = "dynamicDataSource")
- public DataSource dynamicDataSource() {
- DynamicDataSource dynamicDataSource = new DynamicDataSource();
- dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
- Map<Object, Object> dsMap = new HashMap<>();
- dsMap.put(DataSourceConstant.DB_NAME_DM6, primaryDataSource());
- dsMap.put(DataSourceConstant.DB_NAME_DM6_BACKUP, backupDataSource());
- dsMap.put(DataSourceConstant.DB_NAME_DM7, dataSourceDm7());
- dynamicDataSource.setTargetDataSources(dsMap);
- return dynamicDataSource;
- }
- /**
- * 配置事务管理器,使用动态数据源
- */
- @Bean
- public PlatformTransactionManager transactionManager() {
- return new DataSourceTransactionManager(dynamicDataSource());
- }
- }

- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
- import javax.annotation.PostConstruct;
- import javax.sql.DataSource;
- import java.sql.Connection;
- import java.sql.SQLException;
- import java.util.HashMap;
- import java.util.Map;
- /**
- * 数据源切换器
- * @Author: 阿水
- * @Date: 2024-05-24
- */
-
- @Configuration
- public class DataSourceSwitcher extends AbstractRoutingDataSource {
-
- @Autowired
- private DataSource primaryDataSource;
-
- @Autowired
- private DataSource backupDataSource;
-
- private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
-
- @PostConstruct
- public void init() {
- this.setDefaultTargetDataSource(primaryDataSource);
- Map<Object, Object> dataSourceMap = new HashMap<>();
- dataSourceMap.put("primary", primaryDataSource);
- dataSourceMap.put("backup", backupDataSource);
- this.setTargetDataSources(dataSourceMap);
- this.afterPropertiesSet();
- }
-
- @Override
- protected Object determineCurrentLookupKey() {
- return CONTEXT_HOLDER.get();
- }
-
- public static void setDataSource(String dataSource) {
- CONTEXT_HOLDER.set(dataSource);
- }
-
- public static void clearDataSource() {
- CONTEXT_HOLDER.remove();
- }
-
- public boolean isPrimaryDataSourceAvailable() {
- return isDataSourceAvailable(primaryDataSource);
- }
-
- public boolean isBackupDataSourceAvailable() {
- return isDataSourceAvailable(backupDataSource);
- }
-
- private boolean isDataSourceAvailable(DataSource dataSource) {
- try (Connection connection = dataSource.getConnection()) {
- return true;
- } catch (RuntimeException | SQLException e) {
- return false;
- }
- }
- }

这个类通过继承 AbstractRoutingDataSource
实现了动态数据源切换的功能。它使用 ThreadLocal
变量实现线程隔离的数据源标识存储,并提供了设置和清除当前数据源的方法。在 Bean 初始化时,它将主数据源设为默认数据源,并将主数据源和备用数据源添加到数据源映射中。该类还提供了检查数据源可用性的方法,通过尝试获取连接来判断数据源是否可用。
这个类是实现动态数据源切换的核心部分,配合 Spring AOP 可以实现基于注解的数据源切换逻辑,从而实现多数据源和主备数据库的切换功能。
-
- import cn.hutool.core.util.ObjUtil;
- import lombok.extern.slf4j.Slf4j;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Pointcut;
- import org.aspectj.lang.reflect.MethodSignature;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.EnableAspectJAutoProxy;
- import org.springframework.core.annotation.AnnotationUtils;
- import org.springframework.stereotype.Component;
- import java.util.Objects;
- /**
- * AOP切面
- * @Author: 阿水
- * @Date: 2024-05-24
- */
- @Aspect
- @Component
- @Slf4j
- @EnableAspectJAutoProxy
- public class DataSourceAspect {
-
- @Autowired
- private DataSourceSwitcher dataSourceSwitcher;
-
- @Autowired
- private TimeCacheConfig cacheConfig;
-
- @Pointcut("@annotation(com.lps.config.DataSource) || @within(com.lps.config.DataSource)")
- public void dataSourcePointCut() {
- }
-
- /**
- * AOP环绕通知,拦截标注有@DataSource注解的方法或类
- * @param point 连接点信息
- * @return 方法执行结果
- * @throws Throwable 异常信息
- */
- @Around("dataSourcePointCut()")
- public Object around(ProceedingJoinPoint point) throws Throwable {
- // 获取需要切换的数据源
- DataSource dataSource = getDataSource(point);
- log.info("初始数据源为{}", dataSource != null ? dataSource.value() : "默认数据源");
-
- // 设置数据源
- if (dataSource != null) {
- DataSourceUtil.setDB(dataSource.value());
- }
-
- // 处理主数据源逻辑
- if (DataSourceUtil.getDB().equals(DataSourceConstant.DB_NAME_DM6)) {
- handlePrimaryDataSource();
- }
-
- // 获取当前数据源
- String currentDataSource = DataSourceUtil.getDB();
- log.info("最终数据源为{}", currentDataSource);
-
- try {
- // 执行被拦截的方法
- return point.proceed();
- } finally {
- // 清除数据源
- DataSourceUtil.clearDB();
- log.info("清除数据源");
- }
- }
-
- /**
- * 处理主数据库的数据源切换逻辑
- */
- private void handlePrimaryDataSource() {
- // 检查缓存中是否有主数据库挂掉的标记
- if (ObjUtil.isNotEmpty(cacheConfig.timeCacheHc().get("dataSource", false))) {
- // 切换到备用数据源
- DataSourceUtil.setDB(DataSourceConstant.DB_NAME_DM6_BACKUP);
- log.info("切换到备用数据源");
- } else {
- // 检查主数据库状态并切换数据源
- checkAndSwitchDataSource();
- }
- }
-
- /**
- * 检查主数据库状态并在必要时切换到备用数据库
- */
- private void checkAndSwitchDataSource() {
- try {
- // 检查主数据库是否可用
- if (dataSourceSwitcher.isPrimaryDataSourceAvailable()) {
- log.info("主数据源没有问题,一切正常");
- } else {
- // 主数据库不可用,更新缓存并切换到备用数据源
- cacheConfig.timeCacheHc().put("dataSource", "主数据库挂了,boom");
- log.info("主数据源存在问题,切换备用数据源");
- DataSourceUtil.setDB(DataSourceConstant.DB_NAME_DM6_BACKUP);
- }
- } catch (Exception e) {
- // 主数据库和备用数据库都不可用,抛出异常
- throw new RuntimeException("两个数据库都有问题 GG", e);
- }
- }
-
- /**
- * 获取需要切换的数据源
- * @param point 连接点信息
- * @return 数据源注解信息
- */
- private DataSource getDataSource(ProceedingJoinPoint point) {
- MethodSignature signature = (MethodSignature) point.getSignature();
- DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
- if (Objects.nonNull(dataSource)) {
- return dataSource;
- }
- return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
- }
- }

- /**
- * 缓存配置类
- * @Author: 阿水
- * @Date: 2024-05-24
- */
- @Configuration
- public class TimeCacheConfig {
- @Bean
- public TimedCache timeCacheHc() {
- return CacheUtil.newTimedCache(5 * 60 * 1000);
- }
- }
定时缓存,对被缓存的对象定义一个过期时间,当对象超过过期时间会被清理。此缓存没有容量限制,对象只有在过期后才会被移除,详情可以翻阅hutool官方文档
接口方法上使用注解即可,举例:我这里每个方法都被注解 @DataSource
修饰
- /**
- * @author 19449
- * @description 针对表【STUDENT】的数据库操作Mapper
- * @createDate 2024-03-22 22:09:34
- * @Entity com.lps.domain.Student
- */
- @Mapper
- //@DataSource()
- public interface StudentMapper {
- @DataSource()
- Integer selectCountDm6();
- @DataSource(DataSourceConstant.DB_NAME_DM6_BACKUP)
- Integer selectCountDm6Backup();
- @DataSource(DataSourceConstant.DB_NAME_DM7)
- Integer selectCountDm7();
- @SuppressWarnings("MybatisXMapperMethodInspection")
- @DataSource()
- List<Student> selectList();
-
- @DataSource()
- void insert(Student record);
- }

我dmprimary的信息随便写的,可以发现可以自动切换到备用数据库。 可以看到数据源切换毫无问题,主备数据源切换 毫无问题。
通过以上步骤,本次在Spring Boot项目中实现了自定义注解来管理多数据源,并且在主数据库不可用时自动切换到备用数据库。为了提升效率,我们还使用了缓存来记住主数据库的状态,避免频繁的数据库状态检查。这种设计不仅提高了系统的可靠性和可维护性,还能保证在关键时刻系统能够稳定运行。
希望这篇博客能对你有所帮助,如果你有任何问题或建议,欢迎留言讨论。(有问题可以私聊看到就会回)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。