SpringBoot与ShardingSphere整合,重构高并发库存扣减场景的分布式事务模型

订单创建和库存扣减操作必须保证原子性,避免出现超卖或欠卖的情况,利用 ShardingSphere 的分布式事务管理功能,确保跨多个数据库的操作具有一致性。

为什么选择ShardingSphere?

  • XA 事务支持:ShardingSphere 提供了 XA 协议的支持,确保跨多个数据库的操作具有强一致性,即使在分布式环境下也能保持数据的一致性。

  • 柔性事务:除了 XA 事务外,ShardingSphere 还支持柔性事务解决方案,如 Saga 和 BASE 模型,以适应不同的业务场景。

  • 动态添加/删除数据源:可以在运行时动态地添加或删除数据源,而无需重启应用程序,提高了系统的灵活性和可维护性。

  • SQL 解析引擎:ShardingSphere 内置了强大的 SQL 解析引擎,能够解析和改写 SQL 语句,使其适用于分片后的数据库结构。

  • 透明化访问:通过标准的 JDBC 接口访问分片后的数据库,无需关心底层的数据分布情况。

  • 多种部署模式:ShardingSphere 支持多种部署模式,包括代理模式和嵌入式模式。

  • 容器化支持:支持 Docker 和 Kubernetes 等容器化平台,便于自动化部署和管理。

  • 简化开发:通过 Spring 的事务注解(如 @Transactional),开发者可以轻松地进行事务管理,无需手动编写复杂的事务逻辑。

  • 集成 Atomikos:Atomikos 是一个成熟的事务管理器,与 ShardingSphere 结合使用可以提供可靠的分布式事务管理能力。

  • 灵活的分片策略:可以根据不同的业务需求选择合适的分片算法(如 INLINERANGE 等),确保数据均匀分布并优化查询性能。

  • 支持水平分片:ShardingSphere 支持将数据按一定的规则分散到多个数据库实例中,从而实现水平扩展。这对于处理大规模订单和产品数据非常有效。

代码实操

<!-- Spring Boot Starter Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Boot Starter Data JPA -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- MySQL Connector -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <!-- Lombok (optional) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- ShardingSphere JDBC Core -->
    <dependency>
        <groupId>org.apache.shardingsphere</groupId>
        <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
        <version>5.1.2</version>
    </dependency>

    <!-- Atomikos Transaction Manager -->
    <dependency>
        <groupId>com.atomikos</groupId>
        <artifactId>transactions-jta</artifactId>
        <version>5.0.13</version>
    </dependency>

application.yml

server:
  port:8080

spring:
application:
    name:sharding-demo

# 数据库配置
spring:
shardingsphere:
    datasource:
      names:ds0,ds1
      ds0:
        type:com.zaxxer.hikari.HikariDataSource
        driver-class-name:com.mysql.cj.jdbc.Driver
        jdbc-url:jdbc:mysql://localhost:3306/db0?useSSL=false&serverTimezone=UTC
        username:root
        password:root
      ds1:
        type:com.zaxxer.hikari.HikariDataSource
        driver-class-name:com.mysql.cj.jdbc.Driver
        jdbc-url:jdbc:mysql://localhost:3306/db1?useSSL=false&serverTimezone=UTC
        username:root
        password:root
    rules:
      sharding:
        tables:
          t_product:
            actual-data-nodes:ds$->{0..1}.t_product_$->{0..1}
            table-strategy:
              standard:
                sharding-column:product_id
                sharding-algorithm-name:t_product_inline
            key-generate-strategy:
              column:product_id
              key-generator-name:snowflake
          t_order:
            actual-data-nodes:ds$->{0..1}.t_order_$->{0..1}
            table-strategy:
              standard:
                sharding-column:order_id
                sharding-algorithm-name:t_order_inline
            key-generate-strategy:
              column:order_id
              key-generator-name:snowflake
        binding-tables:
          -t_product,t_order
        sharding-algorithms:
          t_product_inline:
            type:INLINE
            props:
              algorithm-expression:t_product_$->{product_id%2}
          t_order_inline:
            type:INLINE
            props:
              algorithm-expression:t_order_$->{order_id%2}
        key-generators:
          snowflake:
            type:SNOWFLAKE
    props:
      sql-show:true

Product

package com.example.demo.entity;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity(name = "t_product")
publicclass Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long productId;
    private String productName;
    private Integer stock;
}

Order

package com.example.demo.entity;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity(name = "t_order")
publicclass Order {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long orderId;
    private Long productId;
    private Integer quantity;
}

Repository

package com.example.demo.repository;

import com.example.demo.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductRepository extends JpaRepository<Product, Long> {
}

OrderRepository

package com.example.demo.repository;

import com.example.demo.entity.Order;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {
}

Service

package com.example.demo.service;

import com.example.demo.entity.Product;
import com.example.demo.repository.ProductRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
publicclass ProductService {

    @Autowired
    private ProductRepository productRepository;

    public Product getProductById(Long productId) {
        return productRepository.findById(productId).orElseThrow(() -> new RuntimeException("Product not found"));
    }

    public void updateStock(Long productId, int quantity) {
        Product product = getProductById(productId);
        product.setStock(product.getStock() - quantity);
        productRepository.save(product);
    }
}

OrderService

package com.example.demo.service;

import com.example.demo.entity.Order;
import com.example.demo.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
publicclass OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private ProductService productService;

    @Transactional
    public void placeOrder(Long productId, int quantity) {
        // 检查库存
        if (!productService.getProductById(productId).getStock().equals(quantity)) {
            thrownew RuntimeException("Insufficient stock");
        }

        // 创建订单
        Order order = new Order();
        order.setProductId(productId);
        order.setQuantity(quantity);
        orderRepository.save(order);

        // 扣减库存
        productService.updateStock(productId, quantity);
    }
}

Controller

package com.example.demo.controller;

import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/products")
publicclass ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/{productId}")
    public Product getProduct(@PathVariable Long productId) {
        return productService.getProductById(productId);
    }

    @PostMapping("/")
    public Product createProduct(@RequestBody Product product) {
        return productService.createProduct(product);
    }
}

OrderController

package com.example.demo.controller;

import com.example.demo.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/orders")
publicclass OrderController {

    @Autowired
    private OrderService orderService;

    @PostMapping("/place-order")
    public String placeOrder(@RequestParam Long productId, @RequestParam int quantity) {
        try {
            orderService.placeOrder(productId, quantity);
            return"Order placed successfully";
        } catch (Exception e) {
            return"Failed to place order: " + e.getMessage();
        }
    }
}

Application

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

测试

curl -X POST http://localhost:8080/orders/place-order \
     -d "productId=1&quantity=5"

Respons

Order placed successfully

关注我,送Java福利

/**
 * 这段代码只有Java开发者才能看得懂!
 * 关注我微信公众号之后,
 * 发送:"666",
 * 即可获得一本由Java大神一手面试经验诚意出品
 * 《Java开发者面试百宝书》Pdf电子书
 * 福利截止日期为2025年06月28日止
 * 手快有手慢没!!!
*/
System.out.println("请关注我的微信公众号:");
System.out.println("Java知识日历");
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值