本文目录导读:

在Java中处理数据库事务,核心是保证一组数据库操作要么全部成功(提交),要么全部失败(回滚),以维护数据的一致性和完整性,主要分为两种主流方式:JDBC原生事务和Spring声明式事务。
下面我们分别介绍这两种方式的处理案例。
JDBC原生事务(低层、手动控制)
这种方式需要手动获取数据库连接,开启事务,执行SQL,然后根据结果选择提交或回滚,适用于学习理解或小型项目。
核心步骤:
connection.setAutoCommit(false);// 关闭自动提交,开启事务- 执行多条SQL语句。
connection.commit();// 所有操作成功,提交事务- 如果发生异常,
connection.rollback();// 回滚所有操作 - 在
finally或 try-with-resources 中关闭资源。
案例代码:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JdbcTransactionExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/your_database";
String username = "root";
String password = "password";
// SQL语句:银行转账 A账户扣100,B账户加100
String sqlDebit = "UPDATE accounts SET balance = balance - 100 WHERE account_id = ?";
String sqlCredit = "UPDATE accounts SET balance = balance + 100 WHERE account_id = ?";
// 使用try-with-resources自动关闭Connection, PreparedStatement
try (Connection conn = DriverManager.getConnection(url, username, password);
PreparedStatement pstmtDebit = conn.prepareStatement(sqlDebit);
PreparedStatement pstmtCredit = conn.prepareStatement(sqlCredit)) {
// 1. 开启事务:关闭自动提交
conn.setAutoCommit(false);
// 2. 执行转账操作
// 扣钱
pstmtDebit.setInt(1, 1); // 账户1扣钱
int affectedRowsDebit = pstmtDebit.executeUpdate();
// 加钱
pstmtCredit.setInt(1, 2); // 账户2加钱
int affectedRowsCredit = pstmtCredit.executeUpdate();
// 模拟一个异常,触发回滚(比如业务校验失败)
// if (someCondition) { throw new RuntimeException("业务异常"); }
// 3. 所有操作成功,提交事务
conn.commit();
System.out.println("转账成功!");
} catch (SQLException e) {
// 4. 发生异常,尝试回滚
// 注意:这里连接对象需要提前获取,或者使用嵌套try-catch
System.err.println("转账失败,尝试回滚...");
// 由于try-with-resources在catch之前关闭连接,我们需要在catch中获取连接对象回滚,
// 通常做法是将Connection声明在try外面,或者使用更复杂的嵌套。
// 为简化演示,这里省略了回滚具体代码,实际应使用 connection.rollback();
e.printStackTrace();
// 更规范的写法是:在catch块中获取connection对象并rollback
}
}
}
注意事项:
- 务必在
finally或try-with-resources中关闭 Connection、Statement、ResultSet 等资源,防止内存泄漏。 - 回滚操作需要 Connection 对象尚未被关闭,JDBC 事务通常需要将 Connection 声明在 try 块外面,以便在 catch 中回滚。
Spring声明式事务(企业级、推荐)
在Spring框架中,最常用的是使用 @Transactional 注解(声明式事务),它基于AOP实现,让开发者只需关注业务逻辑,事务管理由Spring容器自动处理。
核心步骤:
- 配置数据源和事务管理器(通常在Spring Boot中自动配置,如DataSourceTransactionManager或JpaTransactionManager)。
- 在需要事务的类或方法上添加
@Transactional注解。 - Spring会在方法执行前开启事务,执行完正常提交,抛出RuntimeException(默认)时回滚。
案例代码(Spring Boot + MyBatis/Spring Data JPA):
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransferService {
@Autowired
private AccountDao accountDao; // 假设使用MyBatis的Mapper或JPA的Repository
/**
* 转账操作,整个方法在一个数据库事务中执行
* @param fromAccountId 转出方账户ID
* @param toAccountId 转入方账户ID
* @param amount 转账金额
*/
@Transactional(rollbackFor = Exception.class) // 可以指定哪些异常需要回滚
public void transferMoney(int fromAccountId, int toAccountId, double amount) {
// 1. 扣钱
if (accountDao.debitBalance(fromAccountId, amount) != 1) {
// 如果扣钱影响行数不为1,抛出异常触发回滚
throw new IllegalStateException("账户扣款失败,可能账户不存在或余额不足");
}
// 2. 加钱
if (accountDao.creditBalance(toAccountId, amount) != 1) {
// 如果加钱影响行数不为1,抛出异常触发回滚(注意:此时扣钱操作也会被回滚)
throw new IllegalStateException("账户入账失败,可能目标账户不存在");
}
// 3. 其他业务逻辑(如记录日志),如果不抛出异常,全部成功提交
// ...
}
}
@Transactional 关键属性:
propagation:事务传播行为(如 REQUIRED、REQUIRES_NEW、NESTED 等)。REQUIRED(默认):支持当前事务,如果没有则创建新事务。REQUIRES_NEW:挂起当前事务,创建新事务执行当前方法。
isolation:事务隔离级别(如 READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE)。timeout:事务超时时间(秒),超时自动回滚。rollbackFor:指定哪些异常触发回滚(默认只回滚RuntimeException和Error,检查异常不受影响)。noRollbackFor:指定哪些异常不触发回滚。
Spring事务失效的常见原因:
- 方法不能是 private:Spring AOP代理无法拦截私有方法。
- 方法被类内其他方法调用(自调用):
this.doSomething(),没有通过代理对象调用,事务失效,解决方案:注入自身Service代理,或使用AopContext.currentProxy()。 - 异常被方法内 try-catch 捕获且未重新抛出:Spring无法感知异常,不会回滚。
- 事务管理器配置错误:数据源、事务管理器未正确注入。
- 数据库不支持事务(如某些MyISAM引擎的表)。
总结对比
| 特性 | JDBC原生事务 | Spring声明式事务 |
|---|---|---|
| 控制方式 | 手动(setAutocommit, commit, rollback) | 自动(注解驱动,AOP) |
| 代码侵入性 | 高 | 低(只需添加注解) |
| 灵活性 | 高,可精细控制 | 高,通过属性配置 |
| 推荐场景 | 学习、简单项目、非Spring项目 | 企业级应用、Spring/Spring Boot项目 |
| 资源管理 | 需手动关闭(建议try-with-resources) | 自动管理 |
| 异常回滚 | 需要手动捕获异常并调用rollback | 默认回滚RuntimeException,可配置 |
最佳实践:
- 大多数Java Web项目直接使用 Spring声明式事务,结合
@Transactional注解,简洁高效。 - 如果你不使用Spring框架(如纯Servlet/JDBC),则必须使用 JDBC原生事务 手动管理。
- 对于复杂的事务场景(如嵌套事务、长事务),需要理解事务的传播行为和隔离级别,谨慎设计。