分布式事务:TCC

下面是一个详细的 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. 启动和测试

  1. 启动应用:运行 TransactionApplication.java,启动 Spring Boot 应用。
  2. 测试 API:使用 Postman 或 CURL 测试创建事务的 API:
    • URL: http://localhost:8080/transaction/create
    • 方法: POST
    • 参数:
      • accountId: account1
      • orderId: order1
      • amount: 500

4. 总结

以上代码实现了一个简单的 TCC 模式,包含账户余额预留和订单创建的逻辑。通过 Try、Confirm 和 Cancel 方法,确保了分布式事务的最终一致性。在真实应用场景中,建议处理异常、记录日志并增加应对并发的措施。可以考虑集成分布式事务管理工具(如 Seata)以简化实现并增强可靠性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你不懂、、、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值