[ 특정 영화의 모든 이미지와 평균 평점/리뷰 개수 ]
public interface ReviewRepository extends JpaRepository<Review, Long> {
List<Review> findByMovie(Movie movie);
}
먼저 , ReviewRepository 인터페이스에 위와 같이 정의한 뒤 Review에서 필요한 데이터를 추출한다.
@Test
public void testGetMovieReviews() {
Movie movie = Movie.builder().mno(96L).build();
List<Review> result = reviewRepository.findByMovie(movie);
result.forEach(movieReview -> {
System.out.println(movieReview.getReviewnum());
System.out.print("\t"+movieReview.getGrade());
System.out.print("\t"+movieReview.getText());
System.out.print("\t"+movieReview.getMember().getEmail());
System.out.println("----------------------");
});
}
위와 같이 실행하면
no session 오류가 뜨는 것을 볼 수 있다.
no session 오류는 전에 Lazy Loding(지연 로딩)에서 살펴봤던 오류이다.
콘솔창에서
System.out.print("\t"+movieReview.getMember().getEmail());
위의 getMember().getEmail() 을 가져오는 부분에서 오류가 난 것을 확인할 수 있다.
이는 Review에서 Member가 LazyLoding으로 참조하고 있기 때문에 Member 객체를 조회할 수 없기 때문에 발생한다.
여기서 @Transactional을 적용하면?
각 Review 객체의 getMember().getEmail()을 처리할 때마다 Member 객체를 로딩해야 하는 문제가 있다.
따라서 해결방안으로
1) @Query 를 이용하여 조인 처리를 하거나
2) @EntityGraph를 이용하여 Review객체를 가져올 때 Member 객체를 로딩하는 방법이 있다.
@EntityGraph : 엔티티의 특정한 속성을 같이 로딩하도록 표시하는 어노테이션
예를 들어, 기본적으로 JPA는 FATCH 속성값을 LAZY로 지정하는 것이 일반적이다. 이런 상황에서 특정 기능을 수행할 때만 EAGER로딩을 하도록 지정할 수 있다.
@EntityGraph는 attributePaths 속성과 type 속성을 가진다.
attributePaths : 로딩 설정을 변경하고 싶은 속성의 이름을 배열로 명시
type : @EntityGraph를 어떤 방식으로 적용할 것인지 설정 , FATCH 속성값은 Paths에 명시한 속성을 EAGER 처리하고, 나머지는 LAZY처리 / LOAD 속성값은 Paths에 명시한 속성을 EAGER 처리하고, 나머지는 엔티티 클래스에 명시되거나 기본 방식으로 처리
@EntityGraph를 적용해본다.
public interface ReviewRepository extends JpaRepository<Review, Long> {
@EntityGraph(attributePaths = {"member"}, type = EntityGraph.EntityGraphType.FETCH)
List<Review> findByMovie(Movie movie);
}
member 속성을 EAGER 처리하고 나머지는 LAZY 처리한다.
위와 같이 리뷰가 2건인 상황이며 별도의 추가적인 쿼리가 수행되지 않는 것을 볼 수 있다.
[ 회원 삭제 문제와 트랜잭션 처리 ]
만약 M:N(다대다) 관계에서 별도의 매핑 테이블을 구성하고 이를 엔티티로 처리하는 경우, '명사'에 해당하는 데이터를 삭제할 때, 중간에 매핑 테이블에서도 삭제를 해야만 한다.
위의 예제에서 특정한 회원(Member)을 삭제하는 경우 해당 회원이 등록한 모든 리뷰(Review)역시 삭제 되어야 하기 때문에, review 테이블에서 먼저 삭제하고, member 테이블에서 삭제해야 한다.
이 2개의 작업은 하나의 트랜잭션으로 관리될 필요가 있다.
public interface ReviewRepository extends JpaRepository<Review, Long> {
void deleteByMember(Member member);
}
위와 같이 ReviewRepository 인터페이스에 선언해준 뒤,
@Commit
@Transactional
@Test
public void testDeleteMember() {
Long mid = 1L; //Member의 mid
Member member = Member.builder().mid(mid).build();
//순서 주의
reviewRepository.deleteByMember(member);
memberRepository.deleteById(mid);
}
테스트 코드를 입력한다.
여기서 주의할 점은 review테이블에서 먼저 삭제해야 하는점과, @Transactional과 @Commit으로 하나의 트랜잭션으로 처리해야 한다는 점이다.
위와 같이 실행하면 review 테이블에서 3번 반복적으로 실행된 후에 member 테이블을 삭제하는 비효율을 볼 수 있는데,
이는 @Query 를 이용해서 where 절을 지정하면 해결할 수 있다.
@Modifying
@Query("delete from Review mr where mr.member = :member")
void deleteByMember(Member member);
update나 delete를 이용하기 위해선 @Modifying 어노테이션이 반드시 필요하다.
위 쿼리를 실행하면 결과는 아래와 같다.
'코드로 배우는 스프링부트 웹 프로젝트' 카테고리의 다른 글
Springboot) @PostMapping (0) | 2022.01.11 |
---|---|
Springboot) @RestController, @RequestMapping, @GetMapping 예제를 통한 학습 (0) | 2022.01.10 |
Springboot) JPA에서 M:N(다대다) 처리 (1) , N + 1 문제 (0) | 2022.01.03 |
Springboot) @Controller와 @RestController의 차이 (0) | 2021.12.28 |
Springboot) 관계형 DB 설계를 통한 Lazy loding/Eager loding 차이 , @ToString ,JPQL을 이용한 join (0) | 2021.12.13 |