SpringBoot中实现动态数据源切换
SpringBoot中实现动态数据源切换
文章目录
- SpringBoot中实现动态数据源切换
- SpringBoot中实现动态数据源切换
- 基础知识
- 1. 什么是数据源?
- 2. 动态数据源切换的概念
- 3. Spring Boot 中的默认数据源配置
- 4. 动态数据源的挑战
- 5. Spring 中的数据源切换方式
- 设计思路
- 1. 明确应用场景
- 2. 选择数据源切换策略
- 3. 数据源管理
- 4. 动态数据源切换的实现方案
- 4.1 创建动态数据源路由
- 4.2 数据源上下文管理
- 4.3 切换数据源
- 4.4 配置多个数据源
- 4.5 事务管理
- 5. 优雅的切换和性能优化
- 6. 监控与日志
- 实现步骤
- 1. 添加依赖
- 2. 配置多个数据源
- 3. 创建动态数据源路由类
- 4. 创建数据源上下文管理类
- 5. 配置数据源的 Bean
- 6. 在业务逻辑中切换数据源
- 7. 清理数据源上下文
- 8. 事务管理
- 9. 测试与优化
- 切换策略
- 1. 读写分离策略
- 2. 基于线程或上下文的切换策略
- 3. 基于注解的切换策略
- 4. 基于请求类型的切换策略
- 5. 基于事务的切换策略
- 6. 基于业务类型的切换策略
- AOP实现数据源切换
- 1. 概述
- 2. 使用 AOP 实现数据源切换的基本步骤
- 3. 实现步骤
- 1.定义数据源上下文
- 2.定义自定义注解
- 3.编写切面
- 4.使用注解切换数据源
- 5) 配置数据源
- 异常与回滚处理
- 1. 问题背景
- 2. 异常处理的重要性
- 3. 异常和回滚处理的关键步骤
- 1) 事务管理
- 2) 动态数据源切换时的回滚机制
- 3) 捕获与处理异常
- 4) 数据源恢复与错误重试机制
- 5) 跨数据库事务管理(分布式事务)
- 4. 示例:使用 Atomikos 进行分布式事务管理
SpringBoot中实现动态数据源切换
随着业务的不断发展和系统的日益复杂,传统的单一数据库架构往往难以满足高并发、海量数据以及高可用的需求。为了应对这些挑战,分布式数据库架构逐渐成为主流。而在分布式数据库架构中,如何灵活高效地切换数据源,成为了一个关键问题。
这个问题在多租户架构、读写分离、数据库分片等场景中尤为重要。通过动态数据源切换,我们能够根据不同的业务需求、请求来源或者负载情况动态选择不同的数据源,从而提升系统的性能、可扩展性和高可用性。
基础知识
Spring Boot 中实现动态数据源切换 之前,我们需要先了解一些相关的基础知识,这些知识将帮助我们理解动态数据源切换的概念和实施方法。
1. 什么是数据源?
数据源(DataSource)是一个接口,它为 Java 程序提供数据库连接。它通常由数据库连接池实现,比如 HikariCP、C3P0 或 DBCP 等,数据源的作用就是帮助应用程序与数据库建立连接并管理这些连接。
2. 动态数据源切换的概念
动态数据源切换的核心思想是,根据不同的业务场景、请求类型、用户身份或者负载情况,动态地切换到不同的数据源。这种技术在多租户架构、读写分离、数据库分片等场景中尤为重要。
● 多租户架构:每个租户可以使用独立的数据库,或者共享一个数据库中的表,但不同的租户数据是隔离的。
● 读写分离:在高并发的系统中,通常采用主从数据库架构,主库用于写操作,从库用于读操作。动态切换数据源可以根据请求的类型(读/写)自动选择合适的数据源。
● 数据库分片:当单个数据库承载的数据量过大时,可以将数据分散到多个数据库(分片),根据请求的数据范围选择不同的数据库。
3. Spring Boot 中的默认数据源配置
在 Spring Boot 中,默认情况下,我们通过 application.properties 或 application.yml 文件配置数据源。Spring Boot 会自动根据这些配置创建一个单一的数据源对象,应用程序使用该数据源进行数据库操作。
例如,常见的单数据源配置如下:
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
4. 动态数据源的挑战
在一些场景下,我们可能需要基于某些条件切换数据源,比如:
● 根据用户身份切换到不同的数据库。● 根据请求的类型(如读请求或写请求)切换到不同的数据源。
● 在高并发情况下,动态地根据负载选择数据库。
在这种情况下,动态数据源切换将面临如下挑战:
● 事务的隔离性:在切换数据源时,必须确保事务的原子性和一致性,避免因切换数据源导致事务出错或失败。● 数据源的管理:随着数据源数量的增加,如何管理和切换多个数据源,确保高效和稳定的连接池管理,成为一个问题。● 性能问题:频繁的切换数据源可能会导致性能瓶颈,因此需要优化数据源的切换策略,确保系统的高效性。
5. Spring 中的数据源切换方式
在 Spring 框架中,常见的数据源切换方式有两种:
-
基于动态代理的方式:通过 AOP(面向切面编程)技术,结合 Spring 的 AbstractRoutingDataSource 实现动态数据源切换。这种方法通过创建一个动态的代理类,根据业务逻辑动态决定当前使用的数据源。
-
基于注解的方式:通过自定义注解和 AOP 切面结合,标识需要切换数据源的方法或类,在方法调用时动态切换数据源。
设计思路
设计 Spring Boot 中动态数据源切换 的思路时,需要结合应用场景和系统需求来选择合适的架构和技术。
1. 明确应用场景
首先,我们需要明确为什么要实现动态数据源切换。常见的场景包括:
● 多租户架构:每个租户可能使用不同的数据库,或者同一数据库中使用不同的 schema 进行隔离。
● 读写分离:将写操作指向主库,读操作指向从库,从而提高系统的并发处理能力。
● 数据库分片:数据量过大,单一数据库无法满足需求时,通过分片将数据分布到不同的数据库节点。
2. 选择数据源切换策略
根据不同的场景,数据源切换的策略也有所不同。通常有以下几种策略:
● 按请求类型切换:根据请求类型(如读请求或写请求)动态选择数据源。写操作使用主库,读操作使用从库。
● 按用户切换:根据当前用户的身份或租户信息,动态选择数据源。例如,多租户场景下,不同租户可能使用不同的数据库。
● 按业务逻辑切换:根据具体的业务逻辑来选择数据源。比如,特定业务流程可能需要访问某个特定的数据库。
3. 数据源管理
在设计动态数据源切换时,数据源的管理非常重要。需要解决以下问题:
● 连接池的管理:每个数据源都应该有独立的数据库连接池。可以使用 HikariCP 等数据库连接池来管理连接的生命周期和性能。
● 数据源的动态切换:实现动态切换数据源的核心是 AbstractRoutingDataSource
。通过 AbstractRoutingDataSource
,可以在运行时根据某些条件动态选择数据源。
● 事务的一致性:在动态切换数据源时,需要确保事务的一致性。Spring 提供了 @Transactional
注解来保证事务的一致性,但在多数据源环境下,必须确保事务管理能够跨多个数据源正确执行。
4. 动态数据源切换的实现方案
实现动态数据源切换可以通过以下步骤:
4.1 创建动态数据源路由
AbstractRoutingDataSource
是 Spring 提供的一个抽象类,可以通过它实现数据源的动态路由。我们可以继承 AbstractRoutingDataSource
,并重写 determineCurrentLookupKey()
方法,该方法用于获取当前应该使用的数据源的标识。基于该标识,系统可以在多个数据源之间进行切换。
public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSourceType(); // 获取当前的数据源标识}
}
4.2 数据源上下文管理
为了在运行时动态切换数据源,我们需要维护一个上下文来存储当前的数据库标识。通常,可以通过 ThreadLocal 来管理当前线程的数据库标识。可以使用 DataSourceContextHolder 来进行数据源的切换。
public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSourceType(String dataSourceType) {contextHolder.set(dataSourceType);}public static String getDataSourceType() {return contextHolder.get();}public static void clearDataSourceType() {contextHolder.remove();}
}
4.3 切换数据源
在具体的业务逻辑中,可以通过设置 DataSourceContextHolder 来切换数据源。例如:
public class SomeService {public void someMethod() {DataSourceContextHolder.setDataSourceType("master"); // 切换到主库// 执行主库操作}public void anotherMethod() {DataSourceContextHolder.setDataSourceType("slave"); // 切换到从库// 执行从库操作}
}
4.4 配置多个数据源
在 application.yml 或 application.properties 配置文件中,可以配置多个数据源。Spring Boot 会自动为每个数据源生成一个 DataSource Bean。我们需要为每个数据源配置合适的连接池,并根据需要进行切换。
spring:datasource:master:url: jdbc:mysql://localhost:3306/masterusername: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driverslave:url: jdbc:mysql://localhost:3306/slaveusername: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driver
4.5 事务管理
在多数据源的情况下,事务的管理需要特别注意。如果使用的是 @Transactional 注解,Spring 会根据当前的数据源来处理事务。为保证事务一致性,可以使用 PlatformTransactionManager 来管理不同数据源的事务。
5. 优雅的切换和性能优化
动态切换数据源的性能和稳定性是设计的关键。我们可以采取以下策略来优化性能:
●数据源的连接池管理:确保每个数据源都配置了合适的连接池,避免频繁创建和销毁连接。
●懒加载数据源:只有在需要的时候才初始化数据源,避免不必要的资源占用。
●数据源切换的粒度控制:避免频繁切换数据源,尽量减少切换的粒度,降低系统的开销。
6. 监控与日志
为了更好地管理动态数据源切换,我们需要加入监控和日志记录机制,跟踪哪些数据源被切换,当前的数据源是什么,以及执行的 SQL 查询是什么。这有助于在问题出现时进行排查。
实现步骤
实现 Spring Boot 中动态数据源切换 的步骤涉及多个关键环节,包括数据源的配置、动态数据源的切换、事务管理等。
1. 添加依赖
首先,确保你的 Spring Boot 项目中已经引入了 Spring Data JPA 或 MyBatis 等数据访问框架的依赖。然后,添加数据库连接池和 Spring Boot Starter 数据源的依赖。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency><dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId>
</dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
2. 配置多个数据源
在 application.yml
或 application.properties
中配置多个数据源。这里我们以主库和从库为例进行配置。
spring:datasource:master:url: jdbc:mysql://localhost:3306/masterusername: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driverslave:url: jdbc:mysql://localhost:3306/slaveusername: rootpassword: passworddriver-class-name: com.mysql.cj.jdbc.Driver
3. 创建动态数据源路由类
我们需要继承 AbstractRoutingDataSource
类来实现数据源的动态路由。在该类中,通过 determineCurrentLookupKey()
方法来决定当前使用哪个数据源。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;public class DynamicDataSource extends AbstractRoutingDataSource {@Overrideprotected Object determineCurrentLookupKey() {return DataSourceContextHolder.getDataSourceType(); // 获取当前线程的数据源类型}
}
4. 创建数据源上下文管理类
为了在不同的业务逻辑中切换数据源,我们需要创建一个上下文管理类,来管理当前线程的 DataSource。可以使用 ThreadLocal 来存储当前线程的数据源标识。
public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();// 设置当前线程使用的数据源public static void setDataSourceType(String dataSourceType) {contextHolder.set(dataSourceType);}// 获取当前线程的数据源public static String getDataSourceType() {return contextHolder.get();}// 清除当前线程的数据源public static void clearDataSourceType() {contextHolder.remove();}
}
5. 配置数据源的 Bean
接下来,我们需要在 Spring 配置类中注册多个数据源并设置动态数据源。在这个步骤中,动态数据源会根据上下文管理类返回的数据源类型来选择具体的数据源。
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {@Bean@Primarypublic DataSource dataSource() {DynamicDataSource dataSource = new DynamicDataSource();// 配置主库和从库的数据源dataSource.setTargetDataSources(getDataSources()); // 设置数据源dataSource.setDefaultTargetDataSource(masterDataSource()); // 设置默认数据源return dataSource;}// 主库的数据源@Beanpublic DataSource masterDataSource() {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/master");dataSource.setUsername("root");dataSource.setPassword("password");return dataSource;}// 从库的数据源@Beanpublic DataSource slaveDataSource() {HikariDataSource dataSource = new HikariDataSource();dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/slave");dataSource.setUsername("root");dataSource.setPassword("password");return dataSource;}// 返回数据源的 map,Key 是数据源标识(例如:主库或从库)private Map<Object, Object> getDataSources() {Map<Object, Object> dataSources = new HashMap<>();dataSources.put("master", masterDataSource());dataSources.put("slave", slaveDataSource());return dataSources;}
}
6. 在业务逻辑中切换数据源
在业务方法中,我们可以通过调用 DataSourceContextHolder.setDataSourceType() 来切换数据源。这里假设我们要根据业务逻辑切换数据源,比如主库和从库的读写分离。
@Service
public class SomeService {@Transactionalpublic void writeData() {// 切换到主库进行写操作DataSourceContextHolder.setDataSourceType("master");// 执行写操作}@Transactionalpublic void readData() {// 切换到从库进行读操作DataSourceContextHolder.setDataSourceType("slave");// 执行读操作}
}
7. 清理数据源上下文
在每次操作完成后,要确保清除当前线程的数据源标识,以避免影响后续的请求。
public class DataSourceAspect {@After("@annotation(org.springframework.transaction.annotation.Transactional)")public void clearDataSource() {DataSourceContextHolder.clearDataSourceType();}
}
8. 事务管理
在多数据源的情况下,事务管理需要特别小心。Spring 提供的 @Transactional 注解支持跨多个数据源进行事务管理,但需要确保不同数据源的事务管理器是独立的。通常,使用 PlatformTransactionManager 来确保每个数据源的事务能够独立处理。
9. 测试与优化
完成以上步骤后,进行数据源切换的测试,确保数据源能够根据业务逻辑正确切换。在高并发场景下,还需要优化连接池的配置,避免连接池资源浪费。
切换策略
在 Spring Boot 中实现动态数据源切换时,切换策略是一个非常重要的环节。切换策略决定了何时以及如何从一个数据源切换到另一个数据源。
1. 读写分离策略
读写分离是最常见的动态数据源切换策略之一。主库用于写操作,从库用于读操作。数据源切换的策略通常基于操作类型:
● 写操作(Insert/Update/Delete):所有写操作需要访问主库。
● 读操作(Select):所有读操作可以访问从库。
在进行数据操作时,判断当前操作是读操作还是写操作,然后根据操作类型动态切换数据源。
@Service
public class DataService {// 读操作public void readData() {DataSourceContextHolder.setDataSourceType("slave"); // 切换到从库// 执行读操作}// 写操作public void writeData() {DataSourceContextHolder.setDataSourceType("master"); // 切换到主库// 执行写操作}
}
2. 基于线程或上下文的切换策略
此策略适用于每个线程有不同的数据库访问需求,例如基于用户或请求的上下文来决定使用哪个数据源。使用 ThreadLocal
或 RequestContext
来存储当前请求的数据源信息,确保每个请求可以独立切换数据源。通过上下文保存当前请求的数据源,常见的做法是使用 ThreadLocal
来存储每个线程的数据源信息。
public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSourceType(String dataSourceType) {contextHolder.set(dataSourceType);}public static String getDataSourceType() {return contextHolder.get();}public static void clearDataSourceType() {contextHolder.remove();}
}
在请求中根据需要切换数据源:
public void processRequest(String dataSourceType) {DataSourceContextHolder.setDataSourceType(dataSourceType);// 执行数据库操作
}
3. 基于注解的切换策略
基于注解的策略允许在方法或类上直接使用注解来指定数据源,从而简化代码逻辑。这种方式可以更加灵活地在不同的服务方法中指定数据源,通常是使用自定义注解来标记需要使用的具体数据源。创建一个自定义注解,例如 @TargetDataSource,在方法级别使用该注解来切换数据源。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {String value();
}
使用该注解来标记需要切换数据源的方法:
@Service
public class SomeService {@TargetDataSource("master")public void writeData() {// 执行写操作}@TargetDataSource("slave")public void readData() {// 执行读操作}
}
创建一个切面(Aspect)来拦截带有 @TargetDataSource 注解的方法,并动态切换数据源:
@Aspect
@Component
public class DataSourceAspect {@Around("@annotation(targetDataSource)")public Object switchDataSource(ProceedingJoinPoint point, TargetDataSource targetDataSource) throws Throwable {// 切换数据源DataSourceContextHolder.setDataSourceType(targetDataSource.value());try {return point.proceed();} finally {// 切换回默认数据源DataSourceContextHolder.clearDataSourceType();}}
}
4. 基于请求类型的切换策略
在某些场景下,切换策略可以基于请求类型进行判断,例如区分 API 接口、用户身份或请求的 URI 等。通过拦截请求的 URL 或参数来确定使用哪个数据源。可以使用 Spring 的 HandlerInterceptor 或自定义的过滤器来拦截请求并根据请求类型进行数据源切换。
@Component
public class RequestInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String path = request.getRequestURI();if (path.contains("/admin")) {// 切换到主库DataSourceContextHolder.setDataSourceType("master");} else {// 切换到从库DataSourceContextHolder.setDataSourceType("slave");}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清理数据源DataSourceContextHolder.clearDataSourceType();}
}
5. 基于事务的切换策略
在某些复杂场景下,可能需要通过事务来控制数据源的切换,确保同一个事务中的多个操作使用相同的数据源。Spring 提供的 @Transactional 注解可以帮助实现事务管理,在事务中进行数据源的切换。在方法上使用 @Transactional 注解进行事务管理,并在方法内切换数据源。
@Transactional
public void processTransaction() {DataSourceContextHolder.setDataSourceType("master");// 执行事务相关操作
}
6. 基于业务类型的切换策略
有时候业务需求决定了数据源的选择。例如,某些业务模块专门使用主库,另一些业务模块则专门使用从库。通过业务模块类型来决定数据源切换。根据业务模块的类型来动态切换数据源,常见的是通过方法参数或业务配置来决定。
public void processBusinessRequest(String businessType) {if ("financial".equals(businessType)) {DataSourceContextHolder.setDataSourceType("master");} else {DataSourceContextHolder.setDataSourceType("slave");}// 执行业务操作
}
AOP实现数据源切换
使用 AOP(面向切面编程)来实现数据源切换是一种非常常见和优雅的做法。AOP 允许我们在不修改业务代码的情况下,动态地切换数据源。通过 AOP,我们可以在方法执行前或执行后切换数据源,依据方法的特性(如读写操作或注解)来决定使用哪个数据源。
1. 概述
AOP 通过切面(Aspect)来拦截方法的调用,在方法执行前后进行一些操作。在实现动态数据源切换时,通常会基于以下几个要素进行切换:
● 业务逻辑类型(如读或写)
● 自定义注解
● 方法执行的上下文(如请求、用户权限等)
2. 使用 AOP 实现数据源切换的基本步骤
-
定义数据源上下文:使用 ThreadLocal 或类似方式在当前线程中保存当前的数据源。
-
创建自定义注解:用来标记需要切换数据源的方法或类。
-
编写切面:使用 AOP 拦截器来拦截指定方法或类,并根据自定义注解来动态切换数据源。
-
切换数据源:在切面中根据业务逻辑切换数据源。
-
清理数据源:方法执行后清理当前数据源的上下文,避免影响到其他方法。
3. 实现步骤
1.定义数据源上下文
首先,我们需要定义一个 DataSourceContextHolder
类来存储当前的数据源类型。可以使用 ThreadLocal
来确保每个线程都有独立的数据源。
public class DataSourceContextHolder {private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();// 设置当前线程的数据源public static void setDataSourceType(String dataSourceType) {contextHolder.set(dataSourceType);}// 获取当前线程的数据源public static String getDataSourceType() {return contextHolder.get();}// 清除当前线程的数据源public static void clearDataSourceType() {contextHolder.remove();}
}
2.定义自定义注解
我们定义一个注解 @TargetDataSource,用于标记需要切换数据源的方法。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetDataSource {String value(); // 数据源名称
}
3.编写切面
使用 AOP 来创建一个切面类,在方法执行前根据注解中的值来切换数据源。切面会拦截所有使用 @TargetDataSource 注解的方法,并在执行时切换数据源。
@Aspect
@Component
public class DataSourceAspect {// 方法执行前切换数据源@Before("@annotation(targetDataSource)")public void before(JoinPoint point, TargetDataSource targetDataSource) {String dataSource = targetDataSource.value();DataSourceContextHolder.setDataSourceType(dataSource); // 切换数据源}// 方法执行后清理数据源@After("@annotation(targetDataSource)")public void after(JoinPoint point, TargetDataSource targetDataSource) {DataSourceContextHolder.clearDataSourceType(); // 清除数据源}
}
4.使用注解切换数据源
在业务逻辑中,使用 @TargetDataSource 注解标记需要切换数据源的方法。根据注解的值,切面会决定使用哪个数据源。
@Service
public class MyService {@TargetDataSource("master")public void writeData() {// 执行写操作,使用主数据库}@TargetDataSource("slave")public void readData() {// 执行读操作,使用从数据库}
}
5) 配置数据源
在 Spring Boot 配置文件中,配置多个数据源,并确保数据源切换的逻辑正常工作。
spring:datasource:master:url: jdbc:mysql://localhost:3306/masterdbusername: rootpassword: passwordslave:url: jdbc:mysql://localhost:3306/slavedbusername: rootpassword: password
异常与回滚处理
在使用动态数据源切换时,异常和回滚处理是非常重要的,因为在数据源切换的过程中可能会遇到各种异常,且如果没有适当的回滚机制,可能会导致数据不一致或其他严重问题。为了确保系统的可靠性和一致性,我们需要在处理数据源切换时合理地管理事务和异常。
1. 问题背景
在分布式或多数据源的应用场景中,通常需要根据不同的业务需求切换数据源。例如,在读写分离的场景中,读操作使用从数据库,而写操作使用主数据库。动态切换数据源时,涉及多个数据源的连接和事务管理,这使得异常处理和回滚更加复杂。如果在执行操作时遇到异常,没有适当的回滚处理,可能导致数据库中的数据不一致,甚至事务未能正确完成。
2. 异常处理的重要性
动态数据源切换和数据库操作中可能会发生不同类型的异常,这些异常可能来自于:
● 数据源连接失败
● 数据库查询或更新失败
● 事务提交失败
● 跨多个数据源的事务不一致问题
因此,我们必须采取合理的措施来捕获异常并回滚事务,保证系统的稳定性和一致性。
3. 异常和回滚处理的关键步骤
1) 事务管理
对于多数据源的应用,事务管理至关重要。在 Spring 中,我们可以通过 @Transactional 注解来管理事务,它能够确保操作的原子性和一致性。事务注解可以应用于服务层的方法,自动启动和提交事务。
@Service
public class MyService {@Transactionalpublic void handleTransaction() {try {// 切换到主数据源进行写操作dataSourceService.writeData();// 切换到从数据源进行读操作dataSourceService.readData();} catch (Exception e) {// 异常处理和回滚handleException(e);}}private void handleException(Exception e) {// 记录异常信息System.out.println("Exception occurred: " + e.getMessage());// 手动回滚事务(Spring会自动回滚,但可以根据需要自定义)throw new RuntimeException("Transaction failed, performing rollback.");}
}
在 @Transactional
注解下,Spring 会自动为所有参与的操作开启事务,如果有异常抛出,Spring 会自动回滚事务。
2) 动态数据源切换时的回滚机制
当我们进行数据源切换时,必须保证在出现异常时能够正确地进行回滚。为了实现这一点,可以通过以下几种方式:
● 全局回滚:在多数据源环境中,使用一个全局事务管理器(如 Atomikos、Narayana 等)来管理多个数据源的事务。这样可以保证在一个数据源操作失败时,所有相关数据源的操作都会回滚,保证数据一致性。
● 本地回滚:如果只是切换了一个数据源,且没有涉及多个数据源的操作,Spring 的 @Transactional
注解可以确保数据源相关的事务回滚。
3) 捕获与处理异常
在多数据源和多事务的环境下,异常处理尤为重要。以下是一些常见的异常处理策略:
● 捕获并记录异常:及时捕获异常并记录日志,以便追踪和调试问题。
● 事务回滚:在数据源切换时,如果发生异常,必须确保事务能够正确回滚,避免造成数据不一致。
● 根据异常类型判断回滚:使用 @Transactional(rollbackFor = Exception.class) 来控制特定异常时是否回滚事务。例如,可以根据业务需求,选择只针对某些特定异常进行回滚。
@Transactional(rollbackFor = SQLException.class)
public void someMethod() throws SQLException {// 数据库操作
}
4) 数据源恢复与错误重试机制
在动态数据源切换时,数据源的不可用或错误可能导致操作失败。为了提升系统的健壮性,可以加入数据源恢复或重试机制:
● 重试机制:在出现临时性异常时,可以尝试重新连接数据库。Spring 提供了 @Retryable 注解,可以帮助实现方法级别的重试逻辑。
● 数据源切换失败后的恢复机制:在数据源连接失败时,可以切换到备用数据源或进行自动恢复操作。
5) 跨数据库事务管理(分布式事务)
如果涉及多个数据源操作,并且需要保证跨多个数据库的事务一致性,可以使用分布式事务框架来管理。常用的分布式事务框架有:
● Atomikos:一个开源的 Java 分布式事务管理器,支持多数据源的事务管理。
● Seata:一个现代化的分布式事务框架,支持高效地管理分布式事务。
通过这些框架,我们可以保证在多个数据库间操作时的一致性和可靠性。
4. 示例:使用 Atomikos 进行分布式事务管理
@Service
public class MyService {@Transactionalpublic void handleTransaction() throws SQLException {try {// 执行主数据库操作dataSourceService.writeData();// 执行从数据库操作dataSourceService.readData();} catch (Exception e) {// 在分布式事务框架下,异常发生时自动回滚所有操作System.out.println("Exception occurred, performing rollback: " + e.getMessage());throw new RuntimeException("Transaction failed, performing rollback.");}}
}
通过 Atomikos 等分布式事务框架,事务将在多个数据源之间自动进行协调,并保证跨数据源操作的一致性。
相关文章:

SpringBoot中实现动态数据源切换
SpringBoot中实现动态数据源切换 文章目录 SpringBoot中实现动态数据源切换SpringBoot中实现动态数据源切换基础知识1. 什么是数据源?2. 动态数据源切换的概念3. Spring Boot 中的默认数据源配置4. 动态数据源的挑战5. Spring 中的数据源切换方式 设计思路1. 明确应…...

数据结构及排序算法
数据结构 线性结构 ◆线性结构:每个元素最多只有一个出度和一个入度,表现为一条线状。线性表按存储方式分为顺序表和链表。 存储结构: ◆顺序存储:用一组地址连续的存储单元依次存储线性表中的数据元素,使得逻辑上相邻的元素物理上也相邻。 ◆链式存储:存储各数据元素的结点…...

Python基础-元组tuple的学习
在 Python 中,元组(tuple)是一种不可变的序列类型,允许存储不同类型的元素。元组非常类似于列表(list),但与列表不同的是,元组一旦创建,就不能修改其内容。 1 元组的创建…...

【手写公式识别】MEMix: Improving HMER with Diverse Formula Structure Augmentation 论文阅读
发表于:ICME 2024 原文链接:https://ieeexplore.ieee.org/document/10687521 源码:无 Abstract 手写数学表达式识别(HMER)旨在将数学表达式(MEs)的图像转换为相应的LaTeX序列。然而࿰…...

使用deepseek写一个飞机大战游戏
说明: 安装pygame:在运行代码之前,需要先安装 pygame 库。可以通过以下命令安装: pip install pygame图像文件:需要将玩家、敌人和子弹的图像文件(player.png, enemy.png, bullet.png)放在与脚本…...

用Kibana实现Elasticsearch索引的增删改查:实战指南
在大数据时代,Elasticsearch(简称 ES)和 Kibana 作为强大的数据搜索与可视化工具,受到了众多开发者的青睐。Kibana 提供了一个直观的界面,可以方便地对 Elasticsearch 中的数据进行操作。本文将详细介绍如何使用 Kiban…...

C# 封送和远程编程介绍
.NET学习资料 .NET学习资料 .NET学习资料 在 C# 编程领域中,封送(Marshaling)和远程编程(Remote Programming)是两个极为重要的概念,它们为开发者提供了与不同环境、不同进程或不同机器上的代码进行交互的…...

MybatisPlus较全常用复杂查询引例(limit、orderby、groupby、having、like...)
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。以下是 MyBatis-Plus 中常用复杂查询(如 LIMIT、ORDER BY、GROUP BY、HAVING、LIKE 等)的引例: 1. 环境准备…...

02.07 TCP服务器与客户端的搭建
一.思维导图 二.使用动态协议包实现服务器与客户端 1. 协议包的结构定义 首先,是协议包的结构定义。在两段代码中,pack_t结构体都被用来表示协议包: typedef struct Pack {int size; // 记录整个协议包的实际大小enum Type type; …...

Jenkins数据备份到windows FTP服务器
文章目录 背景1. 安装配置 FileZilla Server(Windows)1.1 下载并安装 FileZilla Server1.2 配置 FTP 用户和共享目录 2. 安装并配置 FTP 客户端(CentOS)2.1 在 CentOS 安装 lftp 3. 编写 Jenkins 备份脚本3.1 赋予执行权限3.2 测试…...

【R语言】卡方检验
一、定义 卡方检验是用来检验样本观测次数与理论或总体次数之间差异性的推断性统计方法,其原理是比较观测值与理论值之间的差异。两者之间的差异越小,检验的结果越不容易达到显著水平;反之,检验结果越可能达到显著水平。 二、用…...

ASP.NET Core托管服务
目录 托管服务的异常问题 托管服务中使用DI 托管服务案例:数据的定时导出 场景,代码运行在后台。比如服务器启动的时候在后台预先加载数据到缓存,每天凌晨3点把数据导出到备份数据库,每隔5秒钟在两张表之间同步一次数据。托管服…...

HarmonyOS 5.0应用开发——全局自定义弹出框openCustomDialog
【高心星出品】 文章目录 全局自定义弹出框openCustomDialog案例开发步骤完整代码 全局自定义弹出框openCustomDialog CustomDialog是自定义弹出框,可用于广告、中奖、警告、软件更新等与用户交互响应操作。开发者可以通过CustomDialogController类显示自定义弹出框…...

如何在C++ QT 程序中集成cef3开源浏览器组件去显示网页?
文章目录 1. **准备工作**1.1 下载CEF31.2 配置Qt项目2. **集成CEF3到Qt窗口**2.1 创建Qt窗口容器2.2 初始化CEF33. **处理CEF3消息循环**4. **处理多进程架构**5. **完整代码示例**`main.cpp`6. **常见问题**6.1 黑屏问题6.2 窗口嵌入失败6.3 多进程调试7.**Github源码参考**8…...

深入讲解MyBatis
1. MyBatis 的背景和优势 背景:在 Java 开发中,传统的 JDBC 操作数据库代码繁琐,需要手动管理数据库连接、编写 SQL 语句、处理结果集等,开发效率低且容易出错。MyBatis 应运而生,它通过将 SQL 语句与 Java 代码分离&a…...

使用matlab 对传递函数分析bode图和阶跃函数
如果已知一个系统的传递函数,想看一下bode图,可以通过simulink 建模,但是simulink运行起来相对比较慢,我一般都是直接通过matlab 的m语言写脚本实现。可以快速的获得结果 如 我们有一个一阶低通传递函数 syswn/(swn) 在matlab中…...

2025牛客寒假算法基础集训营5(补题)
C 小L的位运算 显然,如果两次反置的价格小于等于交换的价格,那么直接全部反置就好了。 反之,由于交换一定低于两次反置,我们尽可能用交换来消去不正确的位置。不正确的位置类型只有00,01,10,11&…...

FaceFusion如何设置公开链接和端口
有时候我们想在局域网内的其他设备上使用 FaceFusion,这时候需要设置公开链接和端口。 当你运行 FaceFusion 的时候,会发现有这样的一段提示: To create a public link, set shareTrue in launch().但是这个提示是错的,如果你查…...

神经网络常见激活函数 6-RReLU函数
文章目录 RReLU函数导函数函数和导函数图像优缺点pytorch中的RReLU函数tensorflow 中的RReLU函数 RReLU 随机修正线性单元:Randomized Leaky ReLU 函数导函数 RReLU函数 R R e L U { x x ≥ 0 a x x < 0 \rm RReLU \left\{ \begin{array}{} x \quad x \ge 0…...

计算机网络面经
文章目录 基础HTTPHTTP报文结构 (注意)RPC和http的区别TCPTCP报文结构(注意)IP基础 HTTP HTTP报文结构 (注意) 请求行:请求方法get/post,url,http版本 请求头:用户标识,请求体长度,类型,cookie 请求体:内容 状态行:状态码,状态消息、(http版本) 响应头:内…...

Qt:常用控件
目录 控件概述 控件体系的发展 按钮类控件 QPushButton QRadioButton QCheckBox QToolButton 显示类控件 QLabel QLCDNumber QProgressBar QCalendarWidget 输入类控件 QLineEdit QTextEdit QComboBox QSpinBox QDateEdit & QTimeEdit QDial QSlider …...

算法设计-找第二大数(C++)
一、问题描述 用于在给定的整数数组中找到 第二大值。 二、详细代码 #include<iostream> #include<limits.h> using namespace std; //初始化最大值为a[0],次大值为a[1],遍历一次,每次比较并更新最大值和次大值,最…...

【C++高并发服务器WebServer】-14:Select详解及实现
本文目录 一、BIO模型二、非阻塞NIO忙轮询三、IO多路复用四、Select()多路复用实现 明确一下IO多路复用的概念:IO多路复用能够使得程序同时监听多个文件描述符(文件描述符fd对应的是内核读写缓冲区),能够提升程序的性能。 Linux下…...

redis项目
短信登录 这一块我们会使用redis共享session来实现 商户查询缓存 通过本章节,我们会理解缓存击穿,缓存穿透,缓存雪崩等问题,让小伙伴的对于这些概念的理解不仅仅是停留在概念上,更是能在代码中看到对应的内容 优惠…...

Spring统一修改RequestBody
我们编写RestController时,有可能多个接口使用了相同的RequestBody,在一些场景下需求修改传入的RequestBody的值,如果是每个controller中都去修改,代码会比较繁琐,最好的方式是在一个地方统一修改,比如将he…...

NCV4275CDT50RKG 车规级LDO线性电压调节器芯片——专为新能源汽车设计的高可靠性电源解决方案
产品概述: NCV4275CDT50RKG 是一款符合 AEC-Q100 车规认证的高性能LDO(低压差线性稳压器),专为新能源汽车的严苛工作环境设计。该芯片支持 输出调节为 5.0 V 或 3.3 V,最大输出电流达 450mA,具备超低静态电流…...

前端开发架构师Prompt指令的最佳实践
前端开发架构师Prompt 提示词可作为系统提示词使用,可基于用户的需求输出对应的编码方案。 本次提示词偏向前端开发的使用,如有需要可适当修改关键词和示例。 推荐使用 Cursor 中作为自定义指令使用Cline 插件中作为自定义指令使用在力所能及的范围内使…...

【AI实践】Windsurf AI编程voice对话应用
Android Studio新建一个安卓 hello world 应用,使用gitee插件,推送到个人gitee仓库。 本文要写一个基于GLM4-voice的一个语音对话应用,参考 bigmodel.cn平台和开发文档:智谱AI开放平台 第一轮 打开cursor,model切换到…...

【自学笔记】文言一心的基础知识点总览-持续更新
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 文心一言知识点总览一、文心一言简介二、文心一言的核心功能三、文心一言的技术特点四、文心一言的应用场景五、文心一言的使用技巧六、文心一言的未来发展 总结 文…...

kafka消费端之消费者协调器和组协调器
文章目录 概述回顾历史老版本获取消费者变更老版本存在的问题 消费者协调器和组协调器新版如何解决老版本问题再均衡过程**第一阶段CFIND COORDINATOR****第二阶段(JOINGROUP)**选举消费组的lcader选举分区分配策略 第三阶段(SYNC GROUP&…...