JAVAIARY

M:N 다대다 처리 02 본문

Project/2023.02~ ) Study toy 프로젝트

M:N 다대다 처리 02

shiherlis 2023. 5. 7. 23:08

1. 영화 목록 출력 처리 - 추가

1) 영화 이미지 추가

중간에 영화 이미지를 함께 결합해 줌

@Query("select m, max(mi), avg(coalesce(r.grade,0)), count(distinct r) from Movie m " +
            "left outer join MovieImage mi on mi.movie = m " +
            "left outer join Review r on r.movie = m group by m")
Page<Object[]> getListPage(Pageable pageable);
  • 영화 목록 10개를 가져오는 쿼리가 1회 실행되고,
  • 각 영화의 이미지를 가져오는 쿼리는 각각 실행되기 때문에 총 10번 실행됨

2) N+1 문제

1번의 쿼리로 N개의 데이터를 가져왔는데
N개의 데이터를 처리하기 위해 필요한 추가적인 쿼리가 각 N개에 대하여 수행되는 상황

ex) 1페이지에 해당하는 10개의 데이터를 가져오는 쿼리 1번
      + 10개의 이미지를 가져오는 추가 쿼리 10번 실행  == 총 11번의 쿼리 실행

따라서 중간의 이미지를 1개로 줄여서 처리해준다.

@Query("select m, mi, avg(coalesce(r.grade, 0)), count(distinct r) from Movie m "
        + "left outer join MovieImage mi on mi.movie = m"
        + "left outer join Review r on r.movie = m group by m")
Page<Object[]> getListPage(Pageable pageable);
영화정보
영화 정보, 이미지가
  • 비효율적으로 여러번 실행되는 코드인  max() 처리를 없애고 출력하면 중간에 반복적으로 실행되는 부분 없이, 목록을 구하는 쿼리와 개수를 구하는 쿼리만 실행됨.

3)  특정 영화의 모든 이미지와 평균평점/리뷰 개수 (= 영화 상세페이지) 출력기능 추가

3-1) MovieRepository에 기능 추가

@Query("Select m, mi " +
        "from Movie m left outer join MovieImage mi on mi.movie = m " +
        "where m.mno = :mno")
List<Object[]> getMovieWithAll(Long mno);   // 특정 영화 조회

 

쿼리문 테스트
3번영화의 리뷰 개수

3번영화는 2개의 리뷰가 있고, 4개의 이미지가 있음

3-2) 테스트 작성

@Test
public void testGetMovieWithAll(){
    List<Object[]> result = movieRepository.getMovieWithAll(3L);
    System.out.println(result);
    for (Object[] arr : result){
        System.out.println(Arrays.toString(arr));
    }
}

 

  • 특정 영화의 모든 이미지를 불러옴

3-3) 평점, 리뷰 개수 추가

@Query("Select m, mi, avg(coalesce(r.grade, 0)), count(distinct(r)) " +
        " from Movie m left outer join MovieImage mi on mi.movie = m " +
        " left outer join Review r on r.movie = m" +
        " where m.mno = :mno group by mi, m")
List<Object[]> getMovieWithAll(Long mno);   // 특정 영화 조회
  • group by 영화 이미지
    -> 영화 이미지의 개수만큼 데이터를 만듬
  • coalesce
    - 조회된 값이 null이 아닌 경우 : 조회된 값 반환
    - 조회된 값이 null일 경우 : 사용자가 지정한 값(두 번째 파라미터) 반환

평점평균과 리뷰 개수 추가

4) 특정 영화의 모든 리뷰와 회원의 닉네임

  • 영화 리뷰 조회
  • 영화 리뷰 등록, 수정/삭제 기능

4-1) 리뷰조회 기능 추가 및 테스트 작성

public interface ReviewRepository extends JpaRepository<Review, Long> {
    List<Review> findByMovie(Movie movie);
}
@Test
public void testGetMovieReviews(){
    Movie movie = Movie.builder()
            .mno(3L)
            .build();
    List<Review> result = reviewRepository.findByMovie(movie) ;
    result.forEach(movieReview ->{
        System.out.println(movieReview.getReviewnum());
        System.out.println("\t"+movieReview.getGrade());
        System.out.println("\t"+movieReview.getText());
        System.out.println("\t"+movieReview.getMember().getEmail());
        System.out.println("-----------------------------");
    });
}

  • Review 클래스의 Member에 대한 Fetch 방식이 LAZY 이기 때문에
    한 번에 Review 객체와 Member 객체를 조회할 수 없기 때문에 오류 발생
  • @Transactional 을 적용하더라도 Review 객체의 getMember().getEmail()을 처리할 때마다 Member객체를 로딩해야하는 문제 발생
    • 1) @Query를 이용하여 조인 처리하기
    • 2) @EntityGraph 를 이용하여 Review객체를 가져올 때 Member 객체를 로딩하기

💡@EntityGraph

  • 엔티티의 특정한 속성을 같이 로딩하도록 표시하는 어노테이션
  • 일반적으로 JPA 사용시 연관관계의 Fetch 속성값은 LAZY로 지정하는데,
    entitygraph어노테이션을 통해 특정 기능 수행시에만 EAGER 로딩을 하도록 지정할 수 있다.
  • attributePaths: 로딩 설정을 변경하고 싶은 속성의 이름을 배열로 명시함
  • type: @EntityGraph를 어떤 방식으로 적용할 것인지 설정
  • Fetch 속성값은 attributePaths에 명시한 속성은 Eager, 그렇지 않은 속성은 LAZY로 처리
  • Load 속성값은 attributePaths에 명시한 속성은 Eager, 그렇지 않은 속성은 엔티티 클래스에 명시되거나 기본 방식으로 처리

4-2) @EntityGraph를 적용하여 Member도 같이 로딩할 수 있도록 변경

@EntityGraph 어노테이션 작성 후 테스트 재실행

정상적으로 멤버 조회 가능

5) 회원 삭제 문제와 트랜잭션 처리

'Project > 2023.02~ ) Study toy 프로젝트' 카테고리의 다른 글

M : N (다대다) 관계의 설계와 구현  (0) 2023.04.03
자잘한 이슈 고치기!  (0) 2023.03.13
@RestController와 JSON처리  (0) 2023.03.13
JPQL로 검색하기  (0) 2023.03.13
컨트롤러와 화면 처리  (0) 2023.03.06