Real World Example

Paytm-Style Wallet Transfer

A wallet transfer must debit one wallet and credit another in one transaction. Dirty checking means the service only changes fields; Hibernate writes both UPDATEs at commit, or nothing if an exception rolls back:

@Entity
@Table(name = "wallets")
public class Wallet {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private Long userId;
    @Column(nullable = false, precision = 12, scale = 2)
    private BigDecimal balance;

    public void debit(BigDecimal amount) {
        if (balance.compareTo(amount) < 0) throw new InsufficientBalanceException(id);
        balance = balance.subtract(amount);
    }
    public void credit(BigDecimal amount) { balance = balance.add(amount); }
}

@Service
public class WalletService {
    private final EntityManager em;
    public WalletService(EntityManager em) { this.em = em; }

    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        Wallet from = em.find(Wallet.class, fromId);   // MANAGED
        Wallet to = em.find(Wallet.class, toId);       // MANAGED
        from.debit(amount);                            // may throw -> rollback, no SQL written
        to.credit(amount);
        em.persist(new WalletTransaction(fromId, toId, amount));   // TRANSIENT -> MANAGED
    }   // commit: INSERT transaction, UPDATE from, UPDATE to
}

SQL at Commit

Request and SQL

curl -X POST localhost:8080/api/wallet/transfer -H "Content-Type: application/json" -d '{"fromId":1,"toId":2,"amount":500.00}'

Example output

Hibernate: select w1_0.id,w1_0.balance,w1_0.user_id from wallets w1_0 where w1_0.id=?
Hibernate: select w1_0.id,w1_0.balance,w1_0.user_id from wallets w1_0 where w1_0.id=?
Hibernate: insert into wallet_transactions (amount,from_wallet_id,to_wallet_id) values (?,?,?)
Hibernate: update wallets set balance=?,user_id=? where id=?
Hibernate: update wallets set balance=?,user_id=? where id=?

HTTP/1.1 200 OK
{"status":"SUCCESS","amount":500.00}

Note: Two users transferring from the same wallet at the same moment can still cause a lost update. Real payment systems add optimistic locking with @Version or pessimistic locks — covered in the advance level.