在 Spring Framework 的世界中,使用 管理事务是一项强大的功能。然而,权力越大,责任越大。对此注释的误解和误用可能会导致细微的错误和数据不一致。今天,我们将揭示一些常见的陷阱以及如何避免它们。@Transactional
Spring 应用程序中的一个常见疏忽是在私有方法上使用注解或从同一个 Bean 中调用它,这会导致注解被静默忽略。@Transactional
在深入研究下一个常见错误之前,让我们先来看看一个典型的实际示例,如Spring应用程序中的这个实现所示。@Transactional
AccountService
Account.java
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "account")
public class Account{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String ownerName;
private BigDecimal balance;
}
AccountService.java
@Service
public class AccountService {
@Autowired private AccountRepository accountRepository;
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
try {
Account fromAccount =
accountRepository
.findById(fromAccountId)
.orElseThrow(
() -> new Exception("Account not found: " + fromAccountId));
Account toAccount =
accountRepository
.findById(toAccountId)
.orElseThrow(() -> new Exception("Account not found: " + toAccountId));
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new Exception("Insufficient balance in account: " + fromAccountId);
}
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
accountRepository.save(fromAccount);
// Simulate an error in updating the recipient's account
if (toAccount.getId().equals((long) -1)) {
throw new Exception("Error in processing recipient account");
}
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.save(toAccount);
} catch (Exception e) {
log.error("Error occurred while transferring money ...");
}
}
}
下面是一个过于简化的示例,说明了@Transactional是如何运作的:
import java.sql.Connection;
import java.sql.SQLException;
public class TransactionExample {
public void executeTransaction() {
// Get a database connection
Connection connection = dataSource.getConnection();
try (connection) {
// Begin the transaction
connection.setAutoCommit(false);
// Execute SQL statements here
// Example: INSERT, UPDATE, or DELETE operations
// Commit the transaction
connection.commit();
} catch (SQLException e) {
// Roll back the transaction in case of an error
connection.rollback();
}
}
}
注解标记 Spring 应用程序中事务的开始和结束。当方法用 @Transactiona
时,事务在方法开始后立即开始,并在方法完成时结束。这样,在方法中完成的所有操作要么协同工作,要么根本不发生,从而保持数据的安全性和一致性。
有了这个背景,我们现在就可以深入研究常见的错误
在方法中捕获异常@Transactiona
Spring 中事务管理的一个经常被误解的关键方面涉及处理用 .在此类方法中捕获和处理异常时,可能会无意中阻止事务回滚,从而可能导致意外的数据状态。@Transactional
现在让我们修改上面的代码以抛出错误,以便我们可以回滚
@Service
public class AccountService {
@Autowired private AccountRepository accountRepository;
@Transactional
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) throws Exception {
try {
// ... fetch accounts
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
accountRepository.save(fromAccount);
// Simulate an error in updating the recipient's account
if (toAccount.getId().equals((long) -1)) {
throw new Exception("Error in processing recipient account");
}
toAccount.setBalance(toAccount.getBalance().add(amount));
accountRepository.save(toAccount);
} catch (Exception e) {
log.error("Error occurred while transferring money ...");
throw e;
}
}
}
s但是等等,有一个问题:这仍然不会使事务回滚,因为默认情况下,“事务将在
RuntimeException 和 Error 上回滚,但不会在选中的异常时回滚。
若要覆盖此默认行为,我们必须使用要回滚的异常类设置属性。现在,该函数将如下所示:rollbackFor
@Transactional(rollbackFor = Exception.class)
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) throws Exception {
try {
// ... transaction fails ...
} catch (Exception e) {
log.error("Error occurred while transferring money ...");
throw e;
}
}
请记住,在 Spring 中正确使用涉及两个关键实践:确保将其应用于公共方法或外部 Bean 调用以避免被忽略,以及设置属性以进行精确的事务回滚控制。掌握这些方面对于可靠的事务管理和维护应用程序中的数据完整性至关重要。@Transactional
rollbackFo