Version을 통해 관리하며, 데이터가 수정될 때마다 버전 값이 증가한다
예시
- 트랜잭션 A가 리뷰 데이터를 모두 조회하여 상품 점수를 계산한다
- 트랜잭션 B가 별도의 리뷰 데이터를 추가하거나 삭제한다
- A는 B의 변경 사항을 고려하지 않은 상태로 상품 점수를 업데이트하므로, 결과 데이터의 정합성이 틀어질 수 있다.
즉 전체 엔티티를 조회해서 점수를 계산하려고 하는데, 다른 트랜잭션이 추가 및 삭제가 가능해 정합성이 깨질 수 있다
public class ReviewService {
private final ReviewRepository reviewRepository;
private final ReviewRequestMapper reviewRequestMapper;
private final ApplicationEventPublisher eventPublisher;
@Transactional
public void create(Long productId, ReviewCreateReqDto createReqDto, MultipartFile file) {
Review review = reviewRequestMapper.create(productId, createReqDto);
reviewRepository.save(review);
eventPublisher.publishEvent(new ProductIncreaseEvent(productId, createReqDto.score()));
eventPublisher.publishEvent(new ReviewImageFileEvent(file, review));
}
@Service
@RequiredArgsConstructor
public class OptimisticService {
private final ReviewRepository reviewRepository;
private final ProductRepository productRepository;
// 낙관적 락
@Transactional
public void updateOptimistic(Long productId) {
Product product = getProduct(productId);
ReviewUpdate totalReviewScore = getTotalReviewScore(productId);
product.updateReviewData(totalReviewScore.count(), totalReviewScore.totalScore());
productRepository.modifyProductReviewStats(product.getReviewCount(), product.getScore(), product.getId());
}
private Product getProduct(Long productId) {
return productRepository.findById(productId).orElseThrow(
() -> new CustomApiException(ErrorMessage.NOT_FOUND_PRODUCT.getMessage())
);
}
private ReviewUpdate getTotalReviewScore(Long productId) {
List<Review> reviews = reviewRepository.findAllByOptimistic(productId);
long count = reviews.size();
float totalScore = (float) reviews.stream()
.mapToDouble(Review::getScore)
.sum();
return new ReviewUpdate(count, totalScore);
}
record ReviewUpdate(long count, float totalScore) {
}
}