[혼자왔니] JPA - could not initialize proxy

Update:     Updated:

카테고리:

태그:

영속성 컨텍스트, Proxy, DTO, 트랜잭션, Lazy Loading

상황 설명

image

현재 상황은 이렇다.
User와 Post가 1:N 관계를 맺고 있고, Post 엔티티에서 User에 대해 Lazy 전략으로 설정해 준 상태이다.

image

Client에서 Post를 조회하려고 GET 요청을 보내면 Post를 찾아 DTO로 돌려주는 가장 일반적인 형태로 구현이 되어있다.

에러가 난 코드를 살펴보자…!

// 기존 코드(Controller)
@Parameter(
            name = "X-AUTH-TOKEN",
            description = "로그인 성공 후 AccessToken",
            required = true,
            schema = @Schema(type = "string"),
            in = ParameterIn.HEADER)
@Operation(summary = "게시글 단건 조회", description = "게시글을 조회합니다.")
@GetMapping("/api/post/{postId}")
public SingleResult<PostResponseDto> getPost(@PathVariable Long postId) {
	Post post = postService.findByPostId(postId);

	// Entity -> DTO
	PostResponseDto postResponseDto = PostResponseDto.converToDto(post);
	return responseService.getSingleResult(postResponseDto);
}

GET 메소드를 통해 Post에 대한 정보를 요청하고 있다.
다음과 같은 에러 메시지가 뜬다.

could not initialize proxy - no Session

Post 조회 시 어떤 일이 벌어질까?

image

Post를 조회하면 Post와 Lazy Loading으로 연관된 User는 바로 초기화 되지 않고 필요할 때 정보가 채워지는 Proxy 객체로 채워진다.
즉, 위 그림과 같은 상태가 되는 것이다.

Controller 단에서 Post의 값을 써서 DTO를 채워야하는데 User의 값이 초기화가 되지 않으니 DTO를 만들 수 없었던 것이다!

Lazy Loading 방식이니 DTO를 만들기 위해 데이터를 사용할 때 쿼리를 날려 Proxyr객체를 채우지 않을까?

보통 Service단에서 Transaction으로 묶어 Post를 Repository에서 찾아온다.

// 기존 코드(Service)
@Transactional(readOnly = true)
public Post getPostDetail(Long postId) { 
    Post post = postRepository.findPostById(postId).orElseThrow(CPostNotFoundException::new);
    return post;
}

문제는 다음과 같은 이유때문에 발생한다.

JPA의 영속성 컨텍스트는 보통 트랜잭션과 생명 주기를 같이 한다.

  1. 서비스단에서 트랜잭션이 일어난다.
  2. 컨트롤러 단으로 나오면서 영속성 상태가 끝나버린다..
  3. 따라서, 영속성 컨텍스트에서 관리하지 않기 때문에 User에 필요한 값이 있을 때 쿼리를 날려 Proxy 객체를 채우지 않는다.

해결방법

간단하다.

Entity를 DTO로 바꾸는 작업도 Service 단에서 해주면 된다.
즉, Service 단에서 Entity를 받지 말고 Dto를 받아주면 된다.

// 개선한 코드(Controller)
@Parameter(
            name = "X-AUTH-TOKEN",
            description = "로그인 성공 후 AccessToken",
            required = true,
            schema = @Schema(type = "string"),
            in = ParameterIn.HEADER)
@Operation(summary = "게시글 단건 조회", description = "게시글을 조회합니다.")
@GetMapping("/api/post/{postId}")
public SingleResult<PostResponseDto> getPost(@PathVariable Long postId) {
	// 바로 서비스단에서 DTO를 받도록 변경
	PostResponseDto postResponseDto = postService.findByPostId(postId);
	return responseService.getSingleResult(postResponseDto);
}
// 개선한 코드(Service)
@Transactional(readOnly = true)
public Post getPostDetail(Long postId) { 
    Post post = postRepository.findPostById(postId).orElseThrow(CPostNotFoundException::new);
	
    // Entity -> DTO
    PostResponseDto postResponseDto = PostResponseDto.convertToDto(post);
    return postResponseDto;
}

RUAlone 카테고리 내 다른 글 보러가기

댓글 남기기