学网站开发看什么书,wordpress主题xiu,广州建设技术职业学院有什么专业,教育网站图片在现代的企业应用开发中#xff0c;使用多数据源是一个常见的需求。尤其在关键应用中#xff0c;设置主备数据库可以提高系统的可靠性和可用性。在这篇博客中#xff0c;我将展示如何在Spring Boot项目中通过自定义注解实现多数据源以及主备数据库切换。
在此说明#xff…在现代的企业应用开发中使用多数据源是一个常见的需求。尤其在关键应用中设置主备数据库可以提高系统的可靠性和可用性。在这篇博客中我将展示如何在Spring Boot项目中通过自定义注解实现多数据源以及主备数据库切换。
在此说明
我这里以dm6、dm7来举例多数据源 以两个dm6来举例主备数据库基本大部分数据库都通用举一反三即可。
对于dm6不熟悉但是又要用的可以看我这篇博客
Spring Boot项目中使用MyBatis连接达梦数据库6 1. 环境依赖
首先确保你的Spring Boot项目中已经添加了以下依赖 !-- Lombok依赖用于简化Java代码 --dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/dependency!-- MyBatis Spring Boot Starter依赖用于集成MyBatis和Spring Boot --!-- 注意这里使用1.3.0版本因为DM6不支持1.3以上版本 --dependencygroupIdorg.mybatis.spring.boot/groupIdartifactIdmybatis-spring-boot-starter/artifactIdversion1.3.0/version/dependency!-- Spring Boot Starter AOP依赖用于实现AOP功能 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId/dependency!-- DM6 JDBC驱动用于连接DM6数据库 --dependencygroupIdcom.github.tianjing/groupIdartifactIdDm6JdbcDriver/artifactIdversion1.0.0/version/dependency!-- DM8 JDBC驱动用于连接DM8数据库 --dependencygroupIdcom.dameng/groupIdartifactIdDmJdbcDriver18/artifactIdversion8.1.3.62/version/dependency!-- Hutool工具类库用于简化Java开发 --dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.8.27/version/dependency
2. 配置文件
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.DmDriverjdbc-url: jdbc:dm6://8.8.8.8:12345/xxxxusername: xxxxxxxpassword: xxxxxconnection-test-query: select 1type: com.zaxxer.hikari.HikariDataSourcemaximum-pool-size: 8minimum-idle: 2idle-timeout: 600000max-lifetime: 1800000connection-timeout: 30000validation-timeout: 5000initialization-fail-timeout: 1leak-detection-threshold: 0dm7:driver-class-name: dm.jdbc.driver.DmDriverjdbc-url: jdbc:dm://localhost:5236/xxxxpassword: xxxxxxxxxusername: xxxxxxconnection-test-query: select 1type: com.zaxxer.hikari.HikariDataSourcemaximum-pool-size: 10minimum-idle: 2idle-timeout: 600000max-lifetime: 1800000connection-timeout: 30000validation-timeout: 5000initialization-fail-timeout: 1leak-detection-threshold: 0mybatis:mapper-locations: classpath:/mappers/*.xml # 修改为你的 MyBatis XML 映射文件路径configuration:# log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true
3. 定义数据源相关的常量
/*** 定义数据源相关的常量* Author: 阿水* Date: 2024-05-24*/
public interface DataSourceConstant {String DB_NAME_DM6 dm;String DB_NAME_DM6_BACKUP dmBackup;String DB_NAME_DM7 dm7;
}4. 创建自定义注解 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;}5. 动态数据源类
/*** 动态数据源类* Author: 阿水* Date: 2024-05-24*/
public class DynamicDataSource extends AbstractRoutingDataSource {Overrideprotected Object determineCurrentLookupKey() {return DataSourceUtil.getDB();}
}动态数据源切换的核心实现
在多数据源配置中我们需要一个类来动态决定当前使用的数据源这就是 DynamicDataSource 类。它继承自 Spring 提供的 AbstractRoutingDataSource通过覆盖 determineCurrentLookupKey 方法从 ThreadLocal 中获取当前数据源的标识符并返回该标识符以决定要使用的数据源。
6. 数据源工具类
/*** 数据源工具类* Author: 阿水* Date: 2024-05-24*/
public class DataSourceUtil {/*** 数据源属于一个公共的资源* 采用ThreadLocal可以保证在多线程情况下线程隔离*/private static final ThreadLocalString 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();}
}7. 数据源配置类 /*** 数据源配置类用于配置多个数据源并设置动态数据源。* 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 为默认数据源*/PrimaryBean(name dynamicDataSource)public DataSource dynamicDataSource() {DynamicDataSource dynamicDataSource new DynamicDataSource();dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());MapObject, 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;}/*** 配置事务管理器使用动态数据源*/Beanpublic PlatformTransactionManager transactionManager() {return new DataSourceTransactionManager(dynamicDataSource());}
}8. 数据源切换器 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 {Autowiredprivate DataSource primaryDataSource;Autowiredprivate DataSource backupDataSource;private static final ThreadLocalString CONTEXT_HOLDER new ThreadLocal();PostConstructpublic void init() {this.setDefaultTargetDataSource(primaryDataSource);MapObject, Object dataSourceMap new HashMap();dataSourceMap.put(primary, primaryDataSource);dataSourceMap.put(backup, backupDataSource);this.setTargetDataSources(dataSourceMap);this.afterPropertiesSet();}Overrideprotected 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 可以实现基于注解的数据源切换逻辑从而实现多数据源和主备数据库的切换功能。
9. 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 {Autowiredprivate DataSourceSwitcher dataSourceSwitcher;Autowiredprivate 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);}
}10. 缓存配置类
/*** 缓存配置类* Author: 阿水* Date: 2024-05-24*/
Configuration
public class TimeCacheConfig {Beanpublic TimedCache timeCacheHc() {return CacheUtil.newTimedCache(5 * 60 * 1000);}
}定时缓存对被缓存的对象定义一个过期时间当对象超过过期时间会被清理。此缓存没有容量限制对象只有在过期后才会被移除详情可以翻阅hutool官方文档
超时-TimedCache
11. 运行结果 我dmprimary的信息随便写的可以发现可以自动切换到备用数据库。
12. 结论
通过以上步骤本次在Spring Boot项目中实现了自定义注解来管理多数据源并且在主数据库不可用时自动切换到备用数据库。为了提升效率我们还使用了缓存来记住主数据库的状态避免频繁的数据库状态检查。这种设计不仅提高了系统的可靠性和可维护性还能保证在关键时刻系统能够稳定运行。
希望这篇博客能对你有所帮助如果你有任何问题或建议欢迎留言讨论。有问题可以私聊看到就会回