You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java中不使用任何库或@Transactional实现转账事务?

Great question! Implementing manual transactions without relying on frameworks or @Transactional annotations boils down to enforcing atomicity—making sure either both the withdrawal and deposit complete successfully, or neither does. Let's break down how to do this step by step:

Manual Transaction Implementation for Transfer

1. Understand the Problem with the Original Code

Your initial transfer method has a critical flaw: if fromAccount.withdraw(amount) succeeds but toAccount.deposit(amount) throws an exception (e.g., invalid account, system error), the funds disappear entirely. We need to ensure that if any step fails, all changes are rolled back to restore the original state.

2. Core Principles for Manual Transactions

To build a safe manual transaction, we'll follow three key rules:

  • Fail Fast: Validate all conditions before making any changes to avoid partial operations.
  • Atomic Execution: Use error handling to reverse changes if any step fails.
  • Thread Safety: Prevent race conditions and deadlocks when multiple transfers run concurrently.

3. Step-by-Step Implementation

3.1 First, Secure the Account Class

Ensure the Account class has thread-safe, validated methods to modify balances—this is the foundation of reliable transactions:

public class Account {
    private final String id;
    private double balance;
    private final Object lock = new Object();

    public Account(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public double getBalance() {
        synchronized (lock) {
            return balance;
        }
    }

    public void withdraw(double amount) {
        synchronized (lock) {
            if (amount <= 0) {
                throw new IllegalArgumentException("Withdrawal amount must be positive");
            }
            if (balance < amount) {
                throw new IllegalStateException("Insufficient funds");
            }
            balance -= amount;
        }
    }

    public void deposit(double amount) {
        synchronized (lock) {
            if (amount <= 0) {
                throw new IllegalArgumentException("Deposit amount must be positive");
            }
            balance += amount;
        }
    }

    public Object getLock() {
        return lock;
    }
}

3.2 Implement the Transfer with Manual Transaction Logic

Add pre-validation, error handling for rollbacks, and deadlock prevention to the transfer method:

public class BankService {
    // Custom exception to signal transaction failures
    private static class TransactionFailedException extends Exception {
        public TransactionFailedException(String message) {
            super(message);
        }

        public TransactionFailedException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    public void transfer(Account fromAccount, Account toAccount, double amount) throws TransactionFailedException {
        // Step 1: Fail fast - validate all preconditions first
        if (amount <= 0) {
            throw new TransactionFailedException("Transfer amount must be positive");
        }
        if (fromAccount == null || toAccount == null) {
            throw new TransactionFailedException("Both accounts must be non-null");
        }
        if (fromAccount.getId().equals(toAccount.getId())) {
            throw new TransactionFailedException("Cannot transfer to the same account");
        }
        if (fromAccount.getBalance() < amount) {
            throw new TransactionFailedException("Insufficient funds in source account");
        }

        // Step 2: Prevent deadlocks by locking accounts in a consistent order
        Account firstLockedAccount = fromAccount.getId().compareTo(toAccount.getId()) < 0 ? fromAccount : toAccount;
        Account secondLockedAccount = fromAccount.getId().compareTo(toAccount.getId()) < 0 ? toAccount : fromAccount;

        synchronized (firstLockedAccount.getLock()) {
            synchronized (secondLockedAccount.getLock()) {
                boolean withdrawalCompleted = false;
                try {
                    // Execute withdrawal
                    fromAccount.withdraw(amount);
                    withdrawalCompleted = true;

                    // Execute deposit
                    toAccount.deposit(amount);
                } catch (Exception e) {
                    // Step 3: Roll back if deposit fails after withdrawal succeeded
                    if (withdrawalCompleted) {
                        fromAccount.deposit(amount);
                    }
                    throw new TransactionFailedException("Transfer failed - all changes rolled back", e);
                }
            }
        }
    }
}

4. Key Explanations

  • Fail Fast: We validate all rules before touching account balances, so we don't waste time on transactions that are doomed to fail.
  • Atomicity: The try-catch block ensures that if the deposit fails (for any reason), we immediately reverse the withdrawal. This guarantees either both operations succeed or neither does.
  • Thread Safety & Deadlock Prevention: By locking accounts in a consistent order (based on their ID), we avoid deadlocks where two transfers wait indefinitely for each other's locks. Each account's internal lock also prevents concurrent modifications to its balance.

5. Limitations to Note

  • This works best for simple, in-memory scenarios. For persistent storage (like databases), you'd need to adapt the logic to use database-level rollbacks (e.g., Connection.rollback() instead of in-memory deposits/withdrawals).
  • Complex transactions with multiple steps would get unwieldy with this approach—frameworks like Spring's @Transactional handle edge cases (nested transactions, propagation rules) that are hard to implement manually.

内容的提问来源于stack exchange,提问作者kris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 09:05:30