简体   繁体   English

Spring 引导 - 带乐观锁的并发控制

[英]Spring Boot - Concurrency Control with Optimistic Lock

I've been developing an application in Spring Boot using Spring Web, Spring Data JPA and MySQL where an Item is up for sale and multiple Users can bid on it.我一直在使用 Spring Boot Spring Web、Spring 数据 JPA 和 MySQL 开发一个应用程序,其中一个Item待售,多个Users可以对其出价。 Each Item might have an E-bay like Buy-Now price with the same functionality.每个Item可能有一个类似 E-bay 的Buy-Now价格,具有相同的功能。

Soon I realised that some concurrency issues might come up like a User out bidding another on an Item or 2 Users bidding the Buy-Now price of an Item at the same time.很快,我意识到可能会出现一些并发问题,例如一个用户出价另一个项目或 2 个用户同时出价一个项目的立即购买价格。 After some research I came across the concept of Optimistic Lock and I tried to implement a solution that follows this pattern:经过一些研究后,我遇到了乐观锁的概念,并尝试实现一个遵循这种模式的解决方案:

Entities:实体:

public class User  {

    @Id
    @Column(name = "username", nullable = false, length = 45)
    private String username;

    @Column(name = "first_name", nullable = false, length = 45)
    private String firstName;

    @Column(name = "last_name", nullable = false, length = 45)
    private String lastName;
}


public class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long ID;

    @Column(name = "end_date", nullable = false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date endDate;

    @Column(name = "buy_now")
    private Double buyNow;

    @Column(name = "highest_bid", nullable = false)
    private Double highestBid;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "seller_id", nullable = false)
    private User seller;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "buyer_id")
    private User buyer;
}

Service:服务:

@Service
@AllArgsConstructor
public class ItemService {

    private final ItemRepository itemRepository;
    private final UserRepository userRepository;

    @Transactional
    public void bid(String username, Long itemID, Double amount) {
        
        Date seenDate = TimeManager.now();
        Optional<Item> optionalItem = this.itemRepository.findNonExpiredById(seenDate, itemID);

        if (optionalItem.isEmpty())
            throw new ItemNotFoundException(itemID);

        Item item = optionalItem.get();

        Double seenHighestBID = item.getHighestBid();

        User bidder = this.userRepository.getById(username);

        if (item.getBuyNow() != null && amount >= item.getBuyNow())
            if (this.itemRepository.updateHighestBidAndBuyerWithOptimisticLock(itemID, seenHighestBID, bidder, amount, seenDate) == 0)
                throw new InvalidBidException("TODO");
        
        else
            if (this.itemRepository.updateHighestBidWithOptimisticLock(itemID, seenHighestBID, amount, seenDate) == 0)
                throw new InvalidBidException("TODO");
    }
}

Repository:存储库:

@Transactional
@Query("SELECT i FROM Item a WHERE i.buyer is NULL AND ?1 < a.endDate AND a.ID = ?2")
Optional<Item> findNonExpiredById(Date now, Long itemID);


@Modifying
@Transactional
@Query("UPDATE Item i SET i.highestBid = ?4, a.buyer = ?3 WHERE " +
        "i.ID = ?1 AND " +
        "i.buyer is NULL AND " +
        "?5 < i.endDate AND " +
        "i.highestBid >= ?2 AND " +
        "i.highestBid < ?4")
Long updateHighestBidAndBuyerWithOptimisticLock(Long itemID ,Double seenHighestBID, User bidder,Double amount,Date seenDate);


@Modifying
@Transactional
@Query("UPDATE Item i SET i.highestBid = ?4 WHERE " +
        "i.ID = ?1 AND " +
        "i.buyer is NULL AND " +
        "?4 < i.endDate AND " +
        "i.highestBid >= ?2 AND " +
        "i.highestBid < ?3")
Long updateHighestBidWithOptimisticLock(Long itemID,Double seenHighestBID,Double amount,Date seenDate);

Logic:逻辑:

  1. In Service Layer store the conditions X under which an Item was retrieved在服务层存储检索项目的条件 X
  2. In Repository Layer in both update methods attempt to update the Item that meets the conditions X under the assumption that the highestBid field might have increased and under the restriction that the amount offered is strictly higher than the current highestBid.在 Repository Layer 中,两种更新方法都尝试更新满足条件 X 的 Item,假设highestBid 字段可能已经增加,并且在提供的金额严格高于当前 highestBid 的限制下。

Questions:问题:

  1. Is my code actually concurrency - safe?我的代码实际上并发安全吗?
  2. Is there a simpler way to achieve the same result?有没有更简单的方法来达到相同的结果?
  1. I don't know if your code is concurrency-safe, maybe, but you could test it to be sure我不知道你的代码是否是并发安全的,也许吧,但你可以测试它以确保

  2. You're using JPA but you're ignoring a lot of features it offers you.您正在使用 JPA,但您忽略了它为您提供的许多功能。 You can configure an optimistick-locking strategy on the entities you want to protect, and you will not need to re-implement it yourself like you're doing.您可以在要保护的实体上配置 optimistic-locking 策略,并且您不需要像您正在做的那样自己重新实现它。 It will simplify all your SQL queries because JPA will take care of stale entities and will throw an exception if the entity was updated in another transaction.它将简化您所有的 SQL 查询,因为 JPA 将处理过时的实体,如果该实体在另一个事务中更新,则会抛出异常。

    Moreover, you can execute your business method without relying on SQL since it only a simple entity mutation:此外,您可以在不依赖 SQL 的情况下执行您的业务方法,因为它只是一个简单的实体突变:

     if (item.getBuyNow().= null && amount >= item.getBuyNow()) { item.setHighestBid(amount) item;setBuyer(buyer). } else { item.setHighestBid(amount) }

    The entity state will be flushed at the end of your transaction.实体 state 将在您的交易结束时刷新。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM