目录
Java 中实现事务的几种方法
1. JDBC 原生事务
2. Spring 编程式事务
3. Spring 声明式事务(注解方式)
4. EJB 事务
5. 分布式事务
总结
事务是数据库操作中的重要概念,它确保了一组操作要么全部成功,要么全部失败,从而保证数据的一致性和完整性。在 Java 中,我们有多种方式来实现事务管理。本文将详细介绍几种常用的事务实现方法,并提供相应的代码示例。
1. JDBC 原生事务
JDBC 提供了最基础的事务控制方式,通过 Connection 对象来管理事务。默认情况下,JDBC 是自动提交事务的,我们可以通过手动设置来控制事务。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTransactionExample {
// 数据库连接信息
private static final String URL = "jdbc:mysql://localhost:3306/testdb";
private static final String USER = "root";
private static final String PASSWORD = "password";
public void transferMoney(int fromAccountId, int toAccountId, double amount) {
Connection connection = null;
PreparedStatement debitStmt = null;
PreparedStatement creditStmt = null;
try {
// 获取数据库连接
connection = DriverManager.getConnection(URL, USER, PASSWORD);
// 关闭自动提交,开始事务
connection.setAutoCommit(false);
// 从源账户扣款
String debitSql = "UPDATE accounts SET balance = balance - ? WHERE id = ?";
debitStmt = connection.prepareStatement(debitSql);
debitStmt.setDouble(1, amount);
debitStmt.setInt(2, fromAccountId);
debitStmt.executeUpdate();
// 向目标账户存款
String creditSql = "UPDATE accounts SET balance = balance + ? WHERE id = ?";
creditStmt = connection.prepareStatement(creditSql);
creditStmt.setDouble(1, amount);
creditStmt.setInt(2, toAccountId);
creditStmt.executeUpdate();
// 所有操作成功,提交事务
connection.commit();
System.out.println("转账成功!");
} catch (SQLException e) {
System.err.println("转账失败,回滚事务: " + e.getMessage());
try {
// 发生异常,回滚事务
if (connection != null) {
connection.rollback();
}
} catch (SQLException ex) {
System.err.println("回滚事务失败: " + ex.getMessage());
}
} finally {
// 关闭资源
try {
if (debitStmt != null) debitStmt.close();
if (creditStmt != null) creditStmt.close();
if (connection != null) {
// 恢复自动提交模式
connection.setAutoCommit(true);
connection.close();
}
} catch (SQLException e) {
System.err.println("关闭资源失败: " + e.getMessage());
}
}
}
public static void main(String[] args) {
JDBCTransactionExample example = new JDBCTransactionExample();
// 示例:从账户1向账户2转账100元
example.transferMoney(1, 2, 100.0);
}
}
JDBC 事务的核心 API:
setAutoCommit(false):关闭自动提交,开始事务commit():提交事务rollback():回滚事务setSavepoint():设置保存点,可部分回滚
2. Spring 编程式事务
Spring 框架提供了更灵活的事务管理方式。编程式事务通过 TransactionTemplate 或直接使用 PlatformTransactionManager 来控制事务。
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
public class SpringProgrammaticTransactionExample {
private final TransactionTemplate transactionTemplate;
private final JdbcTemplate jdbcTemplate;
// 通过构造函数注入依赖
public SpringProgrammaticTransactionExample(TransactionTemplate transactionTemplate, JdbcTemplate jdbcTemplate) {
this.transactionTemplate = transactionTemplate;
this.jdbcTemplate = jdbcTemplate;
}
public void transferMoney(int fromAccountId, int toAccountId, double amount) {
// 使用TransactionTemplate执行事务
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
// 从源账户扣款
jdbcTemplate.update(
"UPDATE accounts SET balance = balance - ? WHERE id = ?",
amount, fromAccountId
);
// 向目标账户存款
jdbcTemplate.update(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
amount, toAccountId
);
System.out.println("转账成功!");
} catch (Exception e) {
System.err.println("转账失败,准备回滚: " + e.getMessage());
// 标记事务为回滚状态
status.setRollbackOnly();
}
}
});
}
public static void main(String[] args) {
// 实际应用中,这些会由Spring容器管理
// ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// SpringProgrammaticTransactionExample example = context.getBean(SpringProgrammaticTransactionExample.class);
// 示例调用
// example.transferMoney(1, 2, 100.0);
}
}
Spring 编程式事务的配置(XML 方式):
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
3. Spring 声明式事务(注解方式)
声明式事务是 Spring 中最常用的事务管理方式,它通过注解或 XML 配置来管理事务,无需在代码中显式编写事务控制逻辑。
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
private final JdbcTemplate jdbcTemplate;
// 构造函数注入JdbcTemplate
public AccountService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
/**
* 转账操作,使用@Transactional注解声明事务
* propagation = Propagation.REQUIRED:如果当前没有事务,就新建一个事务;如果已经存在一个事务中,加入到这个事务中
* isolation = Isolation.READ_COMMITTED:读取已提交的数据
* rollbackFor = Exception.class:遇到任何异常都回滚
*/
@Transactional(
propagation = org.springframework.transaction.annotation.Propagation.REQUIRED,
isolation = org.springframework.transaction.annotation.Isolation.READ_COMMITTED,
rollbackFor = Exception.class
)
public void transferMoney(int fromAccountId, int toAccountId, double amount) {
// 从源账户扣款
jdbcTemplate.update(
"UPDATE accounts SET balance = balance - ? WHERE id = ?",
amount, fromAccountId
);
// 模拟可能出现的异常
// if (true) throw new RuntimeException("模拟异常,触发回滚");
// 向目标账户存款
jdbcTemplate.update(
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
amount, toAccountId
);
System.out.println("转账成功!");
}
}
@Transactional 注解的主要属性:
propagation:事务传播行为,如 REQUIRED、REQUIRES_NEW 等isolation:事务隔离级别,如 READ_COMMITTED、REPEATABLE_READ 等rollbackFor:指定哪些异常触发回滚noRollbackFor:指定哪些异常不触发回滚timeout:事务超时时间readOnly:是否为只读事务
4. EJB 事务
EJB(Enterprise JavaBeans)也提供了事务管理功能,主要通过注解来声明事务属性。
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
@Stateless
public class AccountEJB {
@PersistenceContext(unitName = "accountPU")
private EntityManager em;
/**
* 转账操作,使用EJB的@Transactional注解
* Transactional.TxType.REQUIRED:如果当前没有事务,就新建一个事务;如果已经存在一个事务中,加入到这个事务中
*/
@Transactional(Transactional.TxType.REQUIRED)
public void transferMoney(int fromAccountId, int toAccountId, double amount) {
Account fromAccount = em.find(Account.class, fromAccountId);
Account toAccount = em.find(Account.class, toAccountId);
if (fromAccount == null || toAccount == null) {
throw new IllegalArgumentException("账户不存在");
}
if (fromAccount.getBalance() < amount) {
throw new IllegalStateException("余额不足");
}
// 执行转账操作
fromAccount.setBalance(fromAccount.getBalance() - amount);
toAccount.setBalance(toAccount.getBalance() + amount);
// 合并实体状态
em.merge(fromAccount);
em.merge(toAccount);
System.out.println("转账成功!");
}
}
// 账户实体类
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
class Account {
@Id
private int id;
private String name;
private double balance;
// getter和setter方法
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public double getBalance() { return balance; }
public void setBalance(double balance) { this.balance = balance; }
}
EJB 事务的传播级别主要有:
REQUIRED:默认值,如果当前有事务则加入,否则创建新事务REQUIRES_NEW:总是创建新事务SUPPORTS:如果当前有事务则加入,否则非事务执行MANDATORY:必须在已有事务中执行,否则抛出异常NOT_SUPPORTED:非事务执行,如果当前有事务则暂停NEVER:非事务执行,如果当前有事务则抛出异常
5. 分布式事务
在分布式系统中,事务可能涉及多个数据源或服务,这时需要使用分布式事务解决方案。Java 中常用的分布式事务实现包括 JTA(Java Transaction API)和一些开源框架如 Seata、Hmily 等。
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.transaction.UserTransaction;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
@Stateless
public class JTADistributedTransaction {
// 注入JTA事务管理
@Resource
private UserTransaction utx;
// 数据库1连接信息
private static final String DB1_URL = "jdbc:mysql://localhost:3306/db1";
private static final String DB1_USER = "root";
private static final String DB1_PASSWORD = "password";
// 数据库2连接信息
private static final String DB2_URL = "jdbc:mysql://localhost:3306/db2";
private static final String DB2_USER = "root";
private static final String DB2_PASSWORD = "password";
public void distributeTransaction() throws Exception {
Connection conn1 = null;
Connection conn2 = null;
PreparedStatement stmt1 = null;
PreparedStatement stmt2 = null;
try {
// 开始JTA事务
utx.begin();
// 连接第一个数据库并执行操作
conn1 = DriverManager.getConnection(DB1_URL, DB1_USER, DB1_PASSWORD);
// 注意:不要设置autoCommit,由JTA管理
String sql1 = "INSERT INTO logs (message) VALUES (?)";
stmt1 = conn1.prepareStatement(sql1);
stmt1.setString(1, "操作1执行成功");
stmt1.executeUpdate();
// 连接第二个数据库并执行操作
conn2 = DriverManager.getConnection(DB2_URL, DB2_USER, DB2_PASSWORD);
String sql2 = "INSERT INTO records (content) VALUES (?)";
stmt2 = conn2.prepareStatement(sql2);
stmt2.setString(1, "操作2执行成功");
stmt2.executeUpdate();
// 提交事务
utx.commit();
System.out.println("分布式事务执行成功!");
} catch (Exception e) {
System.err.println("分布式事务执行失败,准备回滚: " + e.getMessage());
// 回滚事务
utx.rollback();
throw e;
} finally {
// 关闭资源
if (stmt1 != null) stmt1.close();
if (stmt2 != null) stmt2.close();
if (conn1 != null) conn1.close();
if (conn2 != null) conn2.close();
}
}
}
总结
Java 提供了多种事务实现方式,每种方式都有其适用场景:
JDBC 原生事务:适合简单应用,直接操作数据库连接,控制粒度细。Spring 编程式事务:适合需要在代码中精确控制事务边界的场景。Spring 声明式事务:最常用的方式,通过注解或配置实现,代码侵入性低,适合大多数企业应用。EJB 事务:适合使用 EJB 技术的分布式企业应用。分布式事务:适合跨多个数据源或服务的事务场景,实现复杂但必要时不可替代。
选择合适的事务管理方式需要根据应用的架构、复杂度和性能要求来决定。在实际开发中,Spring 声明式事务因其简洁性和灵活性而被广泛采用。