下面是一个详细的 TCC(Try-Confirm-Cancel)模式的代码实现示例,采用 Java、Spring Boot 和简单的 REST API。为了演示的完整性,我们将涉及到数据库操作,并使用 Spring Data JPA 进行数据持久化。
1. 项目结构
假设我们的项目结构如下:
src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ ├── transaction
│ │ │ ├── TransactionApplication.java
│ │ │ ├── controller
│ │ │ │ └── TransactionController.java
│ │ │ ├── model
│ │ │ │ ├── Account.java
│ │ │ │ └── Order.java
│ │ │ ├── repository
│ │ │ │ ├── AccountRepository.java
│ │ │ │ └── OrderRepository.java
│ │ │ ├── service
│ │ │ │ ├── AccountService.java
│ │ │ │ ├── OrderService.java
│ │ │ │ └── TCCCoordinator.java
│ │ │ └── exception
│ │ │ └── TransactionException.java
│ │ └── config
│ │ └── DatabaseConfig.java
│ └── resources
│ ├── application.properties
│ └── data.sql
2. 实现代码
2.1. TransactionApplication.java
package com.example.transaction;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TransactionApplication {
public static void main(String[] args) {
SpringApplication.run(TransactionApplication.class, args);
}
}
2.2. 实体类
2.2.1. Account.java
package com.example.transaction.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String accountId;
private double balance;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
}
2.2.2. Order.java
package com.example.transaction.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderId;
private String accountId;
private double amount;
private boolean confirmed;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getAccountId() {
return accountId;
}
public void setAccountId(String accountId) {
this.accountId = accountId;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public boolean isConfirmed() {
return confirmed;
}
public void setConfirmed(boolean confirmed) {
this.confirmed = confirmed;
}
}
2.3. Repository
2.3.1. AccountRepository.java
package com.example.transaction.repository;
import com.example.transaction.model.Account;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AccountRepository extends JpaRepository<Account, Long> {
Account findByAccountId(String accountId);
}
2.3.2. OrderRepository.java
package com.example.transaction.repository;
import com.example.transaction.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OrderRepository extends JpaRepository<Order, Long> {
Order findByOrderId(String orderId);
}
2.4. Service
2.4.1. AccountService.java
package com.example.transaction.service;
import com.example.transaction.model.Account;
import com.example.transaction.repository.AccountRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
public boolean tryReserve(String accountId, double amount) {
Account account = accountRepository.findByAccountId(accountId);
if (account != null && account.getBalance() >= amount) {
account.setBalance(account.getBalance() - amount); // Reserve the amount
accountRepository.save(account);
return true;
}
return false; // Insufficient balance
}
public boolean confirmReserve(String accountId, double amount) {
// Here you can implement the logic to finalize the transaction if needed
return true; // Assuming confirmation is successful in this example
}
public boolean cancelReserve(String accountId, double amount) {
Account account = accountRepository.findByAccountId(accountId);
if (account != null) {
account.setBalance(account.getBalance() + amount); // Release the reserved amount
accountRepository.save(account);
return true;
}
return false; // Account not found
}
}
2.4.2. OrderService.java
package com.example.transaction.service;
import com.example.transaction.model.Order;
import com.example.transaction.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
public boolean tryCreateOrder(String orderId, String accountId, double amount) {
Order order = new Order();
order.setOrderId(orderId);
order.setAccountId(accountId);
order.setAmount(amount);
order.setConfirmed(false); // Order is not confirmed yet
orderRepository.save(order); // Save but do not confirm
return true; // Assuming order creation is successful
}
public boolean confirmOrder(String orderId) {
Order order = orderRepository.findByOrderId(orderId);
if (order != null) {
order.setConfirmed(true); // Confirm the order
orderRepository.save(order);
return true;
}
return false; // Order not found
}
public boolean cancelOrder(String orderId) {
Order order = orderRepository.findByOrderId(orderId);
if (order != null) {
orderRepository.delete(order); // Cancel the order
return true;
}
return false; // Order not found
}
}
2.4.3. TCCCoordinator.java
package com.example.transaction.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class TCCCoordinator {
@Autowired
private AccountService accountService;
@Autowired
private OrderService orderService;
public void executeTCC(String accountId, String orderId, double amount) {
// Try Phase
boolean accountReserved = accountService.tryReserve(accountId, amount);
boolean orderCreated = orderService.tryCreateOrder(orderId, accountId, amount);
if (accountReserved && orderCreated) {
// Confirm Phase
if (!accountService.confirmReserve(accountId, amount)) {
orderService.cancelOrder(orderId); // If confirmation fails, cancel order
}
if (!orderService.confirmOrder(orderId)) {
accountService.cancelReserve(accountId, amount); // If order confirmation fails, cancel reserve
}
} else {
// If Try phase fails, execute Cancel
if (!accountReserved) {
orderService.cancelOrder(orderId); // Cancel the order if account reserve failed
}
if (!orderCreated) {
accountService.cancelReserve(accountId, amount); // Cancel reserve if order creation failed
}
}
}
}
2.5. Controller
2.5.1. TransactionController.java
package com.example.transaction.controller;
import com.example.transaction.service.TCCCoordinator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/transaction")
public class TransactionController {
@Autowired
private TCCCoordinator tccCoordinator;
@PostMapping("/create")
public ResponseEntity<String> createTransaction(@RequestParam String accountId, @RequestParam String orderId, @RequestParam double amount) {
tccCoordinator.executeTCC(accountId, orderId, amount);
return ResponseEntity.ok("Transaction processed successfully");
}
}
2.6. Exception Handling
2.6.1. TransactionException.java
package com.example.transaction.exception;
public class TransactionException extends RuntimeException {
public TransactionException(String message) {
super(message);
}
}
2.7. 数据库配置
2.7.1. DatabaseConfig.java
package com.example.transaction.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
@Configuration
@EnableJpaRepositories(basePackages = "com.example.transaction.repository")
public class DatabaseConfig {
// Configure DataSource bean here if necessary
}
2.8. application.properties
# Database configuration (example for H2 in-memory database)
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
2.9. data.sql
INSERT INTO account (account_id, balance) VALUES ('account1', 1000);
INSERT INTO account (account_id, balance) VALUES ('account2', 2000);
3. 启动和测试
- 启动应用:运行
TransactionApplication.java
,启动 Spring Boot 应用。 - 测试 API:使用 Postman 或 CURL 测试创建事务的 API:
- URL:
http://localhost:8080/transaction/create
- 方法:
POST
- 参数:
accountId
:account1
orderId
:order1
amount
:500
- URL:
4. 总结
以上代码实现了一个简单的 TCC 模式,包含账户余额预留和订单创建的逻辑。通过 Try、Confirm 和 Cancel 方法,确保了分布式事务的最终一致性。在真实应用场景中,建议处理异常、记录日志并增加应对并发的措施。可以考虑集成分布式事务管理工具(如 Seata)以简化实现并增强可靠性。