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. Each Item
might have an E-bay like Buy-Now price with the same functionality.
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. 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. 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. 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.
Moreover, you can execute your business method without relying on SQL since it only a simple entity mutation:
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.