Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public ResponseEntity<BiddingResponse> registerBidding(
@Valid @RequestBody RegisterBiddingRequest request,
@Parameter(hidden = true) @JwtAuthorization User bidder
) {
BiddingResponse response = biddingService.registerBidding(request, auctionId, bidder);
BiddingResponse response = biddingService.registerBiddingWithPessimisticLock(request, auctionId, bidder);
return ResponseEntity.ok(response);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package dev.handsup.auction.repository.auction;

import java.util.Optional;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import dev.handsup.auction.domain.Auction;
import dev.handsup.auction.domain.auction_field.AuctionStatus;
import dev.handsup.user.domain.User;
import jakarta.persistence.LockModeType;

public interface AuctionRepository extends JpaRepository<Auction, Long>, AuctionQueryRepository {

Expand All @@ -20,4 +24,12 @@ public interface AuctionRepository extends JpaRepository<Auction, Long>, Auction

Slice<Auction> findBySeller_IdAndStatusOrderByCreatedAtDesc(
Long sellerId, AuctionStatus auctionStatus, Pageable pageable);

@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("select a from Auction a where a.id = :id")
Optional<Auction> findByIdWithPessimisticLock(Long id);

@Lock(LockModeType.OPTIMISTIC)
@Query("select a from Auction a where a.id = :id")
Optional<Auction> findByIdWithOptimisticLock(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,5 @@ public interface BiddingRepository extends JpaRepository<Bidding, Long> {
Slice<Bidding> findByBidderAndAuction_StatusOrderByAuction_CreatedAtDesc(
User bidder, AuctionStatus auctionStatus, Pageable pageable);

Long countByAuctionId(Long auctionId);
}
46 changes: 41 additions & 5 deletions core/src/main/java/dev/handsup/bidding/service/BiddingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,49 @@ public class BiddingService {

@Transactional
@DistributeLock(key = "'auction_' + #auctionId") // auctionId 값을 추출하여 락 키로 사용
public BiddingResponse registerBidding(RegisterBiddingRequest request, Long auctionId, User bidder) {
public BiddingResponse registerBiddingWithDistributedLock(RegisterBiddingRequest request, Long auctionId, User bidder) {
Auction auction = getAuctionById(auctionId);

validateBiddingPrice(request.biddingPrice(), auction); // 경매 입찰 최고가보다 입찰가 높은지 확인
auction.updateCurrentBiddingPrice(request.biddingPrice()); // 경매 입찰 최고가 갱신
auction.increaseBiddingCount(); // 경매 입찰 수 + 1
validateBiddingPrice(request.biddingPrice(), auction);
updateAuctionOnNewBidding(request, auction);
Bidding bidding = BiddingMapper.toBidding(request.biddingPrice(), auction, bidder);
sendBiddingNotification(bidder, auction); //알림 전송

sendBiddingNotification(bidder, auction);

return BiddingMapper.toBiddingResponse(biddingRepository.save(bidding));
}

@Transactional
public BiddingResponse registerBiddingWithPessimisticLock(RegisterBiddingRequest request, Long auctionId, User bidder) {
Auction auction = auctionRepository
.findByIdWithPessimisticLock(auctionId)
.orElseThrow(() -> new NotFoundException(AuctionErrorCode.NOT_FOUND_AUCTION));

validateBiddingPrice(request.biddingPrice(), auction);
updateAuctionOnNewBidding(request, auction);
Bidding bidding = BiddingMapper.toBidding(request.biddingPrice(), auction, bidder);

sendBiddingNotification(bidder, auction);

return BiddingMapper.toBiddingResponse(biddingRepository.save(bidding));
}

@Transactional
public BiddingResponse registerBiddingWithOptimisticLock(RegisterBiddingRequest request, Long auctionId, User bidder) {
Auction auction = auctionRepository
.findByIdWithOptimisticLock(auctionId)
.orElseThrow(() -> new NotFoundException(AuctionErrorCode.NOT_FOUND_AUCTION));

validateBiddingPrice(request.biddingPrice(), auction);
updateAuctionOnNewBidding(request, auction);
Bidding bidding = BiddingMapper.toBidding(request.biddingPrice(), auction, bidder);

sendBiddingNotification(bidder, auction);

return BiddingMapper.toBiddingResponse(biddingRepository.save(bidding));
}


@Transactional(readOnly = true)
public PageResponse<BiddingResponse> getBidsOfAuction(Long auctionId, Pageable pageable) {
Slice<BiddingResponse> biddingResponsePage = biddingRepository
Expand Down Expand Up @@ -116,4 +147,9 @@ private void sendBiddingNotification(User bidder, Auction auction) {
NotificationType.BIDDING_CREATED
);
}

private void updateAuctionOnNewBidding(RegisterBiddingRequest request, Auction auction) {
auction.updateCurrentBiddingPrice(request.biddingPrice()); // 경매 입찰 최고가 갱신
auction.increaseBiddingCount(); // 경매 입찰 수 + 1
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package dev.handsup.bidding.service;

import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.stereotype.Service;

import dev.handsup.bidding.dto.request.RegisterBiddingRequest;
import dev.handsup.bidding.dto.response.BiddingResponse;
import dev.handsup.user.domain.User;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@RequiredArgsConstructor
@Slf4j
public class OptimisticLockAuctionFacade {
private final BiddingService biddingService;
private static final long RETRY_DELAY_MS = 50;

public BiddingResponse registerBidding(
RegisterBiddingRequest request, Long auctionId, User bidder
) throws InterruptedException {
int retryCount = 0;
while (true) {
try {
BiddingResponse response = biddingService.registerBiddingWithOptimisticLock(request, auctionId,
bidder);
log.info("경매 입찰 성공 - auctionId: {}, bidderId: {}, 총 시도횟수: {}",
auctionId, bidder.getId(), retryCount++);
return response;
} catch (ObjectOptimisticLockingFailureException e) {
{
log.info("경매 입찰 실패 - auctionId: {}, bidderId: {}, 총 시도횟수: {}",
auctionId, bidder.getId(), retryCount);
Thread.sleep(RETRY_DELAY_MS);
}
}
}
}
}

This file was deleted.

Loading