Spring Boot JpaRepository无法执行save:交易实体未保存无报错
我在构建一个Spring Boot简易应用时遇到实体保存问题:银行账户能正常存入数据库,但交易实体无法保存。
诡异的是没有任何报错信息,调用transactionRepository.save()后方法直接退出,应用仍正常运行,控制台无提示,调试也没发现异常。
以下是makeTransaction()方法,执行到transactionRepository.save(...)前一切正常:
public void makeTransaction(Iban sourceIban, Iban destinationIban, double amount) throws Exception { BankAccountEntity sourceAccount = getBankAccountByIban(sourceIban); BankAccountEntity destinationAccount = getBankAccountByIban(destinationIban); Date currentDate = new Date(); TransactionEntity sourceTransaction = new TransactionEntity(sourceAccount, sourceIban, destinationIban, currentDate, amount); TransactionEntity destinationTransaction = new TransactionEntity(destinationAccount, sourceIban, destinationIban, currentDate, amount); if (sourceAccount.getAmountDeductible() < amount || amount < 0) { // TODO amount checker throw new Exception(ILLEGAL_TRANSACTION_AMOUNT_TEXT); // TODO change exception type } sourceAccount.reduceBalance(amount); sourceAccount.addTransaction(sourceTransaction); destinationAccount.increaseBalance(amount); destinationAccount.addTransaction(destinationTransaction); bankAccountRepository.save(sourceAccount); bankAccountRepository.save(destinationAccount); transactionRepository.save(sourceTransaction); transactionRepository.saveAndFlush(destinationTransaction); }
类中仓库初始化代码:
private final BankAccountRepository bankAccountRepository; private final TransactionRepository transactionRepository; public BankAccountService(@Autowired BankAccountRepository bankAccountRepository, @Autowired TransactionRepository transactionRepository) { this.bankAccountRepository = bankAccountRepository; this.transactionRepository = transactionRepository; }
BankAccountRepository定义:
public interface BankAccountRepository extends JpaRepository<BankAccountEntity, UUID> { Optional<BankAccountEntity> findByIban(String iban); }
TransactionRepository定义:
public interface TransactionRepository extends JpaRepository<TransactionEntity, UUID> {}
TransactionEntity实体类:
@Entity @Table(name = "transactions") public class TransactionEntity { @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "transaction_id", nullable = false) @Type(type = "uuid-char") private Long transactionId; private String sourceIban; private String destinationIban; private long dateExecuted; private double amount; @ManyToOne @JoinColumn(name = "bank_account_id") private BankAccountEntity bankAccount; public TransactionEntity() { } public TransactionEntity(BankAccountEntity bankAccount, Iban sourceIban, Iban destinationIban, Date dateExecuted, double amount) { this.bankAccount = bankAccount; this.sourceIban = sourceIban.toString(); this.destinationIban = destinationIban.toString(); this.amount = amount; this.dateExecuted = dateExecuted.getTime(); } public BankAccountEntity getBankAccount() { return bankAccount; } public void setBankAccount(BankAccountEntity bankAccount) { this.bankAccount = bankAccount; } public Long getTransactionId() { return transactionId; } public void setTransactionId(Long transactionId) { this.transactionId = transactionId; } }
问题分析与解决方法
1. 主键类型不匹配(核心问题)
TransactionRepository继承JpaRepository<TransactionEntity, UUID>,指定主键类型为UUID,但TransactionEntity的transactionId却是Long类型,还错误添加了@Type(type="uuid-char")注解,这会导致JPA无法正确生成和映射主键,最终静默失败。
修复步骤:
修改TransactionEntity的主键类型为UUID:
@Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "transaction_id", nullable = false) @Type(type = "uuid-char") // 保持该注解适配数据库CHAR(36)类型的UUID private UUID transactionId; // 同时更新对应的getter和setter public UUID getTransactionId() { return transactionId; } public void setTransactionId(UUID transactionId) { this.transactionId = transactionId; }
2. 缺失事务管理注解
makeTransaction()方法涉及多个数据库操作(更新账户、保存交易),没有添加@Transactional注解会导致操作不在同一个事务上下文内,可能出现部分执行、静默失败的情况。
修复步骤:
在makeTransaction()方法上添加@Transactional注解:
import org.springframework.transaction.annotation.Transactional; @Transactional public void makeTransaction(Iban sourceIban, Iban destinationIban, double amount) throws Exception { // 原有代码不变 }
3. 关联关系的级联优化(可选)
如果BankAccountEntity与TransactionEntity是双向关联,可通过配置级联操作简化代码,无需单独调用transactionRepository.save():
在BankAccountEntity的交易集合字段上添加级联属性:
@OneToMany(mappedBy = "bankAccount", cascade = CascadeType.ALL, orphanRemoval = true) private List<TransactionEntity> transactions = new ArrayList<>(); // addTransaction方法确保双向关联正确 public void addTransaction(TransactionEntity transaction) { transactions.add(transaction); transaction.setBankAccount(this); // 关键:设置反向关联 }
这样在调用bankAccountRepository.save(sourceAccount)时,会自动级联保存关联的交易实体,无需再单独调用交易仓库的保存方法。
内容的提问来源于stack exchange,提问作者fipps omat

