[英]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:逻辑:
Questions:问题:
I don't know if your code is concurrency-safe, maybe, but you could test it to be sure我不知道你的代码是否是并发安全的,也许吧,但你可以测试它以确保
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.