JPA fetch

‍박태우·2024년 10월 22일

nbc_spring

목록 보기
24/28

위 프로젝트에서 댓글 그리고 해당 댓글의 좋아요 수를 담당하였다.

1. 배경

  • 컨트롤러 코드

@GetMapping("/articles/{articleId}/comment")
	public ResponseEntity<Page<CommentResponseDto>> getComments(
		@PathVariable Long articleId,
		@RequestParam(required = false, defaultValue = "0") int page,
		@RequestParam(required = false, defaultValue = "10") int size,
		@CookieValue(value = "Authorization", required = false) String authorization) {
		return ResponseEntity.status(HttpStatus.OK)
			.body(commentService.getComments(articleId, page, size, authorization));
	}

위와 같은 컨트롤러를 통해 Page 객체를 반환하는 컨트롤러를 구현하였다.
CookieValue 의 Authorization 키를 가지는 값(value) 를 통해 토큰을 가져오고 그 값을
service 단으로 옮긴다.

  • 서비스 코드
public Page<CommentResponseDto> getComments(Long articleId, int page, int size, String authorization) {
		if (!articleRepository.existsById(articleId)) {
			throw new IllegalArgumentException("Article id " + articleId + " not found");
		}

		Pageable pageable = PageRequest.of(page, size, Sort.by("updatedAt").descending());
		Page<Comment> comments = commentRepository.findAllByArticleId(pageable, articleId);

		Member author = findByEmail(authorization);

		return comments.map(comment -> {
				boolean isLiked = commentLikeRepository.existsByCommentIdAndMemberId(comment.getId(), author.getId());
				return CommentResponseDto.of(comment, isLiked);
			}
		);
	}

서비스 단의 코드로서, 기본적으로 수정일 기준 내림차순으로 Page객체에 담도록 하였다. Comment 엔티티를 CommentResponseDto 로 변환하도록 한 후 Page 객체로 반환하였다.


2. 에러 발생

실제로 테스트를 해보니 아래와 같은 오류가 발생하였다.
에러 로그를 보니 아래와 같았다.

  • 실제로 왜 이런 오류가 나타나나 살표보니 아래 코드에서 LAZY 로 지정된 지연 로딩된 엔티티를 초기화 하려고 할때 발생한다고 한다.
    이 예외는 세션이 닫혀 있는 상태에서 비지연 로딩이 필요한 객체에 접근할 때 발생한다. 예를 들어, Comment를 로드한 후 Comment의 author를 참조할 때 세션이 닫혀 있으면 이 에러가 발생한다고 한다.
public static CommentResponseDto from(Comment comment) {
		return CommentResponseDto.builder()
			.id(comment.getId())
			.articleId(comment.getArticle().getId())
			.nickname(comment.getAuthor().getNickname())
			.body(comment.getBody())
			.createdAt(comment.getCreatedAt())
			.updatedAt(comment.getUpdatedAt())
			.build();
	}

위 코드에서 닉네임을 얻어오는데 이때 getAuthor().getNickname() 을 실행할때 위와 같은 오류가 발생하는 것이다.

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "member_id")
	@OnDelete(action = OnDeleteAction.CASCADE)
	private Member author;

위 처럼 Comment 엔티티에서 Member 를 불러올때 LAZY 즉 지연 로딩을 이용하기 때문에 위 오류가 발생할 수 있는 것이다.

  • 해결하는 방법

1) 위 코드에서 fetch 타입을 Eager 로 변경하면 된다.
이렇게 되면 Comment 조회 시 Author 까지 모두 불러오게 된다.
=> BUT 이 방법은 권장되지 않는다. Comment 만 조회 하고 싶을 때 필요 없는 Author 까지 조회하게 되어 오버헤드가 발생할 수 있기 때문!!

2) 리포지토리에서 조인하기

public interface CommentRepository extends JpaRepository<Comment, Long> {
	@Query("SELECT c FROM Comment c JOIN FETCH c.author WHERE c.article.id = :articleId")
	Page<Comment> findAllByArticleId(Pageable pageable, @Param("articleId") Long articleId);
}

위 서비스에서 사용되었던 메서드인 findAllByArticleId 를 선언 할때 JPQL 을 이용하여 한번에 join하여 가져올 수 있도록 하였다.


3. 해결

fetch 타입을 변경하면서 문제를 해결하였다.

profile
잘 부탁드립니다.

0개의 댓글