Blog

[TroubleShooting] 연관관계에서의 일관성 유지를 위한 수정 중에 발생한 Review에서 StoreId를 못가져오는 상황

작성날짜
2023/09/20
작성자
최종 편집 일시
2023/11/15 00:53
지금 문제는 ReviewService.java 에서
// 리뷰 작성 @Transactional public ResponseEntity<StatusResponseDto> createReview(ReviewRequestDto reviewRequestDto, User user) { return handleServiceRequest(() -> { Store store = checkStoreExist(reviewRequestDto.getStoreId()); Review review = new Review(reviewRequestDto, user); review.setStore(store); reviewRepository.save(review); return new StatusResponseDto("리뷰를 등록했습니다.", 200); }); }
Java
복사
에서 review.setStore(store);이걸 삭제하면서 발생
우선 이전까지의 변경사항으로는 DB, 내부 Java객체들의 데이터 일관성 유지를 위해 orphanRemoval을 설정하면서, 이에 따라 불필요한 코드들을 제거하고있었다.
Review와 Store는 N:1 의 양방향 관계를 가지고있다.
이말은 곧 리뷰는 Store의 id를 조회해야하고
가게는 Review들을 불러와서 조회해야 하기 때문에 양방향 연관관계가 필요하다.
여기서 N에 해당되는 Review는 다음과 같이 Store와 연관관계를 설정한다.
@JsonIgnore @JoinColumn(name = "store_id") @ManyToOne(fetch = FetchType.LAZY) private Store store;
Java
복사
여기서 1에 해당되는 Store는 다음과 같이 ReviewList와 연관관계를 설정한다.
@OneToMany(mappedBy = "store", orphanRemoval = true) private List<Review> reviewList = new ArrayList<>();
Java
복사
orphanRemoval = true를 통해서 데이터의 일관성을 보장 받을 수 있다.( 또는 편의 메소드를 활용 할 수 있지만 비교적 긴 Setter 메소드를 작성해야 한다. 명시적으로 보이지만, 코드의 간결성 중에서 선택해야 하는 문제이다. 또한 @JsonIgnore과 함께 사용하기 어려운 문제, 코드의 간결성을 위해서 위 처럼 @JsonIgnoreorphanRemoval을 사용하는 것으로 통일했다. 이러면 데이터 일관성과 직렬화의 무한루프를 방지 할 수 있다.
하지만 이 과정을 통해 Setter메소드를 삭제했기 때문에 위 Service로직에서 review.setStore(store);이부분이 불필요해졌고 삭제하면서 storeId를 찾지 못하는 문제가 발생했다.
따라 올라가면 결국 Review review = new Review(reviewRequestDto, user); 객체를 생성하는 부분에서 storeId를 인자로 전달하지 못하고 있었고, RequestDto도 마찬가지로 매개변수로 storeId가 전달받지 못하므로 null을 처리하고있었다.
따라서 수정된 코드는 다음과 같다.
// 리뷰 작성 @Transactional public ResponseEntity<StatusResponseDto> createReview(ReviewRequestDto reviewRequestDto, User user) { return handleServiceRequest(() -> { Store store = checkStoreExist(reviewRequestDto.getStoreId()); Review review = new Review(reviewRequestDto, user, store); reviewRepository.save(review); return new StatusResponseDto("리뷰를 등록했습니다.", 200); }); }
Java
복사
리뷰 객체를 만들때 store객체를 인자로 넘겨준다. store객체는 그 윗줄 에서 store존재 여부 확인 메소드의 결과로 생성되는데, 존재확인 메소드에서 store객체를 불러오게 된다.
// 가게 체크 private Store checkStoreExist(Long id) { return storeRepository.findById(id).orElseThrow( () -> new IllegalArgumentException("해당 가게는 존재하지 않습니다.") ); }
Java
복사
이 부분에서 storeRepository로부터(DB로부터) storeId를 통해 해당 가게가 존재하는지 체크하는데, 존재하면 그 가게를 Service의 호출부로 전달하게 되고, 거기서 store가 지정되게 된다.
따라서 리뷰 객체를 만들 때 이 가게의 정보(그중 storeId)가 인자로 전달되며, Review엔티티의 생성자(객체를 생성하는 틀)에서 storeId를 리뷰 객체에 담을 수 있게 된다.
public Review(ReviewRequestDto reviewRequestDto, User user, Store store) { this.review = reviewRequestDto.getReview(); this.star = reviewRequestDto.getStar(); this.user = user; this.store = store; }
Java
복사
이러한 흐름으로 연관관계부터 잃어버린 storeId까지 설정을 완료하여 정상적으로 POSTMAN에서 작동하는 것이 확인되었다.
POST http://localhost:8080/api/reviews 200 426 ms POST /api/reviews HTTP/1.1 Content-Type: application/json User-Agent: PostmanRuntime/7.33.0 Accept: */* Cache-Control: no-cache Postman-Token: bdd987ed-65ae-4270-b040-0aa0a61c1d9d Host: localhost:8080 Accept-Encoding: gzip, deflate, br Connection: keep-alive Content-Length: 74 Cookie: Authorization=Bearer%20eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJlbWFpbDVAZW1haWwuY29tIiwiYXV0aCI6IlVTRVIiLCJleHAiOjE2OTUxNzM3MDcsImlhdCI6MTY5NTE3MDEwN30.lqQgDHCa-D-O95HlfDVZAaoh7O4hDchM-kOTr3C6qxQ { "storeId" : 2, "review" : "리뷰내용3", "star" : 5 } HTTP/1.1 200 OK
Java
복사
Hibernate또한 정상적으로 insert 쿼리를 생성하여 DB에 저장을 진행했다.
Hibernate: /* insert for com.sparta.springcafeservice.entity.Review */insert into reviews (created_at,modified_at,review,star,store_id,user_id) values (?,?,?,?,?,?)
Java
복사