lock refunds against duplicates

This commit is contained in:
2026-04-15 15:48:17 -06:00
parent 7730c0f80a
commit d7179665d9
4 changed files with 17 additions and 2 deletions

View File

@@ -1,14 +1,23 @@
package com.petshop.backend.repository; package com.petshop.backend.repository;
import com.petshop.backend.entity.Refund; import com.petshop.backend.entity.Refund;
import jakarta.persistence.LockModeType;
import org.springframework.data.jpa.repository.JpaRepository; 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 org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.util.List; import java.util.List;
import java.util.Optional;
@Repository @Repository
public interface RefundRepository extends JpaRepository<Refund, Long> { public interface RefundRepository extends JpaRepository<Refund, Long> {
List<Refund> findByCustomerIdOrderByCreatedAtDesc(Long customerId); List<Refund> findByCustomerIdOrderByCreatedAtDesc(Long customerId);
List<Refund> findAllByOrderByCreatedAtDesc(); List<Refund> findAllByOrderByCreatedAtDesc();
List<Refund> findBySaleId(Long saleId); List<Refund> findBySaleId(Long saleId);
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT r FROM Refund r WHERE r.id = :id")
Optional<Refund> findByIdForUpdate(@Param("id") Long id);
} }

View File

@@ -1,10 +1,12 @@
package com.petshop.backend.repository; package com.petshop.backend.repository;
import com.petshop.backend.entity.Sale; import com.petshop.backend.entity.Sale;
import jakarta.persistence.LockModeType;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import org.springframework.data.jpa.repository.JpaRepository; 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.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@@ -42,5 +44,9 @@ public interface SaleRepository extends JpaRepository<Sale, Long> {
List<Sale> findByOriginalSaleSaleId(Long originalSaleId); List<Sale> findByOriginalSaleSaleId(Long originalSaleId);
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT s FROM Sale s WHERE s.saleId = :id")
Optional<Sale> findByIdForUpdate(@Param("id") Long id);
Optional<Sale> findByCartCartId(Long cartId); Optional<Sale> findByCartCartId(Long cartId);
} }

View File

@@ -112,7 +112,7 @@ public class RefundService {
@Transactional @Transactional
public RefundResponse updateRefundStatus(Long id, String status) { public RefundResponse updateRefundStatus(Long id, String status) {
Refund refund = refundRepository.findById(id) Refund refund = refundRepository.findByIdForUpdate(id)
.orElseThrow(() -> new ResourceNotFoundException("Refund not found")); .orElseThrow(() -> new ResourceNotFoundException("Refund not found"));
Refund.RefundStatus newStatus; Refund.RefundStatus newStatus;

View File

@@ -107,7 +107,7 @@ public class SaleService {
} }
if (sale.getIsRefund() && request.getOriginalSaleId() != null) { if (sale.getIsRefund() && request.getOriginalSaleId() != null) {
Sale originalSale = saleRepository.findById(request.getOriginalSaleId()) Sale originalSale = saleRepository.findByIdForUpdate(request.getOriginalSaleId())
.orElseThrow(() -> new ResourceNotFoundException("Original sale not found with id: " + request.getOriginalSaleId())); .orElseThrow(() -> new ResourceNotFoundException("Original sale not found with id: " + request.getOriginalSaleId()));
sale.setOriginalSale(originalSale); sale.setOriginalSale(originalSale);
} }