diff --git a/peauty-customer-api/src/main/java/com/peauty/customer/implementaion/workspace/WorkspaceAdapter.java b/peauty-customer-api/src/main/java/com/peauty/customer/implementaion/workspace/WorkspaceAdapter.java index a04710bf..e969e06a 100644 --- a/peauty-customer-api/src/main/java/com/peauty/customer/implementaion/workspace/WorkspaceAdapter.java +++ b/peauty-customer-api/src/main/java/com/peauty/customer/implementaion/workspace/WorkspaceAdapter.java @@ -18,7 +18,9 @@ import com.peauty.persistence.designer.workspace.WorkspaceEntity; import com.peauty.persistence.designer.workspace.WorkspaceRepository; import lombok.RequiredArgsConstructor; +import org.springframework.orm.ObjectOptimisticLockingFailureException; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -100,18 +102,33 @@ public Workspace findByDesignerId(Long userId) { } @Override - public Workspace registerReviewStats(Long designerId, ReviewRating newRating) { - WorkspaceEntity workspaceEntity = workspaceRepository.getByDesignerId(designerId) - .orElseThrow(() -> new PeautyException(PeautyResponseCode.NOT_EXIST_WORKSPACE)); - List bannerImageEntities = bannerImageRepository.findByWorkspaceId(workspaceEntity.getId()); - Workspace workspace = WorkspaceMapper.toDomain(workspaceEntity, bannerImageEntities); - // 리뷰 작성 로직 - workspace.registerReviewStats(newRating); - // 엔티티 변환 후 저장 - workspaceEntity = WorkspaceMapper.toEntity(workspace, designerId); - workspaceRepository.save(workspaceEntity); - - return workspace; + @Transactional + public Workspace registerReviewStats(Long designerId, ReviewRating newRating +// TODO: InterruptedException을 WorkspacePort에서 Workspace registerReviewStats(Long designerId, ReviewRating newRating) throws InterruptedException; 이렇게 걸어주는 것이 맞을까? +// 아니면 내부 try-catch에서 InterruptedException을 거는게 맞을까? + ) { + while (true) { + try { + WorkspaceEntity workspaceEntity = workspaceRepository.findByDesignerIdWithOptimisticLock(designerId) // Workspace 엔티티에서 id를 호출해올 때, 버전을 확인 + .orElseThrow(() -> new PeautyException(PeautyResponseCode.NOT_EXIST_WORKSPACE)); + List bannerImageEntities = bannerImageRepository.findByWorkspaceId(workspaceEntity.getId()); + Workspace workspace = WorkspaceMapper.toDomain(workspaceEntity, bannerImageEntities); + // 리뷰 작성 로직 + workspace.registerReviewStats(newRating); + // 엔티티 변환 후 저장 + workspaceEntity = WorkspaceMapper.toEntity(workspace, designerId); + workspaceRepository.save(workspaceEntity); + + return workspace; + } catch (ObjectOptimisticLockingFailureException e) { + try { + Thread.sleep(10); // 10ms 대기 // 저장 작업을 수행하는 하나의 실행 단위(Thread) + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); // 현재 스레드의 인터럽트 상태 복구 + throw new PeautyException(PeautyResponseCode.INTERNAL_SERVER_MAINTENANCE); + } + } + } } public Workspace updateReviewStats(Long designerId, ReviewRating oldRating, ReviewRating newRating) { diff --git a/peauty-domain/build.gradle b/peauty-domain/build.gradle index 1cf8e098..b3f4c0f0 100644 --- a/peauty-domain/build.gradle +++ b/peauty-domain/build.gradle @@ -11,6 +11,7 @@ dependencies { testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' testImplementation 'org.mockito:mockito-core' testImplementation 'org.assertj:assertj-core' + } test { diff --git a/peauty-domain/src/main/java/com/peauty/domain/response/PeautyResponseCode.java b/peauty-domain/src/main/java/com/peauty/domain/response/PeautyResponseCode.java index fed8dec9..c18b4d4f 100644 --- a/peauty-domain/src/main/java/com/peauty/domain/response/PeautyResponseCode.java +++ b/peauty-domain/src/main/java/com/peauty/domain/response/PeautyResponseCode.java @@ -51,6 +51,7 @@ public enum PeautyResponseCode { INVALID_BIRTHDATE("1229", "Invalid Your Puppy Birthday", "현재 날짜보다 이후를 선택했습니다."), CONTAINS_NON_EXISTING_DESIGNERS("1230", "Designer Not Found", "존재하지 않는 디자이너가 포함되어있습니다."), NOT_FOUND_REVIEWER_WRITTEN_REVIEW("1231", "Not found reviewer written review", "해당 리뷰를 작성한 사용자를 찾을 수 없습니다."), + INTERNAL_SERVER_MAINTENANCE("1232", "Performing Internal Server Maintenance. Try Later", "내부 서버 점검 중이니, 잠시 후 다시 등록해주세요."), // 비딩 관련 (1300 ~ 1350) WRONG_BIDDING_PROCESS_STEP_DESCRIPTION("1300", "Wrong Bidding Process Step Description", "잘못된 입찰 프로세스입니다."), diff --git a/peauty-persistence/src/main/java/com/peauty/persistence/designer/workspace/WorkspaceEntity.java b/peauty-persistence/src/main/java/com/peauty/persistence/designer/workspace/WorkspaceEntity.java index c8f77e13..ca49bd15 100644 --- a/peauty-persistence/src/main/java/com/peauty/persistence/designer/workspace/WorkspaceEntity.java +++ b/peauty-persistence/src/main/java/com/peauty/persistence/designer/workspace/WorkspaceEntity.java @@ -69,4 +69,7 @@ public class WorkspaceEntity extends BaseTimeEntity { @Column(name = "phone_number", nullable = false) private String phoneNumber; + @Version + private Integer version; + } diff --git a/peauty-persistence/src/main/java/com/peauty/persistence/designer/workspace/WorkspaceRepository.java b/peauty-persistence/src/main/java/com/peauty/persistence/designer/workspace/WorkspaceRepository.java index fe6008e5..9850f0f8 100644 --- a/peauty-persistence/src/main/java/com/peauty/persistence/designer/workspace/WorkspaceRepository.java +++ b/peauty-persistence/src/main/java/com/peauty/persistence/designer/workspace/WorkspaceRepository.java @@ -1,6 +1,8 @@ package com.peauty.persistence.designer.workspace; +import jakarta.persistence.LockModeType; 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; @@ -21,4 +23,8 @@ @Query("SELECT w FROM WorkspaceEntity w WHERE w.address LIKE CONCAT(:baseAddress, '%')") List findByBaseAddress(@Param("baseAddress") String baseAddress); + @Lock(LockModeType.OPTIMISTIC) + @Query("SELECT w FROM WorkspaceEntity w WHERE w.designerId = :designerId") + Optional findByDesignerIdWithOptimisticLock(@Param("designerId") Long designerId); + } diff --git a/peauty-persistence/src/main/java/com/peauty/persistence/review/ReviewMapper.java b/peauty-persistence/src/main/java/com/peauty/persistence/review/ReviewMapper.java index 3e090649..fda3c0ed 100644 --- a/peauty-persistence/src/main/java/com/peauty/persistence/review/ReviewMapper.java +++ b/peauty-persistence/src/main/java/com/peauty/persistence/review/ReviewMapper.java @@ -83,8 +83,6 @@ public static Review toReviewDomain( .toList()) .reviewRating(reviewEntity.getReviewRating()) .build(); - - } }