在 java jdbc 开发中,使用声明式事务的核心做法是将数据库连接的生命周期管理与 spring 框架的事务管理器绑定,通过 @transactional 注解让容器自动处理提交或回滚。具体实现时,需配置 datasourcetransactionmanager 并指定数据源,同时在业务方法上添加该注解,spring aop 会在方法执行前开启事务,成功则提交,异常则回滚。若未捕获异常且方法抛出运行时异常,事务会自动回滚;若手动捕获了异常,则需显式调用 transactionaspectsupport.currenttransactionstatus().setrollbackonly() 才能触发回滚。

如何配置基于 JDBC 的声明式事务管理器
配置声明式事务的第一步是定义一个继承自 JdbcDaoSupport 或直接注入 JdbcTemplate 的 Bean,并确保其关联的 DataSource 已被正确初始化。接着,需要创建一个 DataSourceTransactionManager Bean,将上述数据源注入其中。最后,启用 @EnableTransactionManagement 以激活注解驱动的事务代理机制。这样,Spring 就能拦截带有 @Transactional 的方法,并使用 JDBC 原生的 Connection.setAutoCommit(false) 来开启事务。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
@Configuration
public class JdbcTxConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
上述代码展示了最简化的配置方式。关键在于 DataSourceTransactionManager 的实例化,它内部持有对 DataSource 的引用。当 Spring 容器启动时,它会扫描所有被 @Transactional 标注的类和方法,并为它们生成代理对象。这些代理对象在执行目标方法前后,会利用 DataSource 获取当前线程绑定的 Connection,从而确保同一事务内的所有 JDBC 操作共享同一个物理连接。
为什么我的事务没有按预期回滚?
事务未按预期回滚通常是因为异常类型不符合默认规则,或者异常被方法内部的 try-catch 块吞掉了。Spring 的 @Transactional 默认只在抛出 RuntimeException 或 Error 时才会触发回滚。如果业务逻辑中抛出了检查型异常(Checked Exception),如 SQLException 未被包装,事务将正常提交。此外,如果在方法内部捕获了异常但没有重新抛出,也没有手动标记回滚,Spring 代理层感知不到错误,也会执行提交操作。

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
public class OrderService {
@Transactional
public void processOrder() {
try {
// 模拟数据库操作
throw new RuntimeException("Simulated failure");
} catch (Exception e) {
// 必须手动标记回滚,否则事务会提交
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
System.out.println("Caught exception, marking rollback");
}
}
}
为了解决这个问题,可以在 @Transactional 注解中指定 rollbackFor 属性,例如 @Transactional(rollbackFor = Exception.class),这样即使抛出检查型异常也会回滚。另一种做法是在 catch 块中调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),如上例所示。这行代码显式告诉事务管理器当前事务状态为“仅回滚”,后续无论是否抛出异常,最终都会执行回滚逻辑。务必注意,不要忽略异常,也不要静默处理,否则会导致数据不一致。
在嵌套事务中子事务失败会影响父事务吗
在嵌套事务场景中,子事务失败是否影响父事务取决于传播行为(Propagation Behavior)。默认情况下,@Transactional 使用 REQUIRED 传播行为,这意味着如果父方法已经在事务中,子方法将加入该现有事务,而不是创建新事务。此时,子事务的任何回滚都会导致整个父事务回滚,因为它们共享同一个数据库连接和事务上下文。若要隔离子事务的影响,需使用 REQUIRES_NEW 传播行为,这会挂起父事务并开启一个全新的独立事务。
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRED)
public void payOrder() {
// 父事务开始
updateOrderStatus();
try {
chargeCreditCard(); // 子事务,若失败则整体回滚
} catch (Exception e) {
// 记录日志,但根据需求决定是继续还是回滚
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void chargeCreditCard() {
// 新事务开始,独立于父事务
// 即使这里失败,也不会影响 updateOrderStatus 的提交
}
}
在上述代码中,payOrder 方法开启了 REQUIRED 事务,而 chargeCreditCard 使用了 REQUIRES_NEW。如果 chargeCreditCard 抛出异常,它会先尝试回滚自己的独立事务,然后异常向上传播给 payOrder。由于 payOrder 捕获了异常,它可以选择继续执行或重新抛出。如果重新抛出,父事务也会回滚。这种设计允许在支付失败时保留订单状态更新,便于后续人工干预或重试,而不必撤销整个业务流程。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











