JPA 얕은 복사와 깊은 복사

Kevin·2024년 3월 12일
2

JPA

목록 보기
10/14
post-thumbnail

서론

과거 카카오 테크 탬퍼스를 수강할 당시 강사님께서 DTO의 생성자를 통해 DTO 객체를 만들 때 깊은 복사를 해야 View애서 Converter 할 때 Lazy Loading 문제가 안생긴다는 말씀을 해주셨다.

이 때 깊은 복사가 뭐고, 그 반대의 개념은 얕은 복사는 무엇인지 그리고 왜 깊은 복사를 하지 않을 때 Lazy Loading이 발생하는지에 대해서 알아보자.


얕은 복사와 깊은 복사

깊은 복사에 대해서 이야기 하기 전 우리는 먼저 Lazy Loading 방식을 알 필요가 있다.

간단한 아래의 예시를 통해서 이야기 해보자.

Post

@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToOne(fetch = FetchType.LAZY)
    private Post post;
    
    public static Post from(String title){
        return Post.builder()
                .title(title)
                .build();
    }
}

User

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String nickname;

}

PostUser는 N:1, 즉 Many To One 관계이다.

이 때 지연 로딩으로 설정해두었는데, 지연 로딩이란 간단하게 설명을 하면 참조된 엔티티가 실제로 사용될 때 DB로의 쿼리를 통해서 가져오는 것이다.

그렇다면 실제로 사용될 때란 언제인가?

바로 해당 값이 필요할 때라고 이해를 하면 될 것 같다.

더 직관적으로 이야기 하면, getter를 통해서 해당 엔티티의 필드 값을 호출할 때를 의미한다.

예를 들어보자.

    @Transactional(readOnly = true)
    public PostResDTO getPostById(Long id) throws RuntimeException {
        Post post = postRepository.findById(id).orElseThrow(() -> new RuntimeException("post가 존재하지 않습니다."));

        return PostResDTO.from(post);
    }

위와 같이 간단하게 Id 값으로 Post 엔티티를 조회하는 코드가 있다.

이 때 메서드의 반환 값으로 PostResDTO 객체의 정적 팩터리 메서드를 통해 객체를 생성 후에 리턴해준다.

PostResDTO의 코드를 봐보자.

@Getter
@Builder
public class PostResDTO {

    private String title;

    private User user;

    public static PostResDTO from(Post post) {
        return PostResDTO.builder()
                .title(post.getTitle())
                .user(post.getUser())
                .build();
    }
}

이 때 Post 엔티티의 필드 값들을 통해서 PostResDTO 객체를 생성 후에 리턴해준다.

좋다.

이러면 아무 문제 없는 코드 아닌가?

만약 이렇게 생각하시는 분이 있으시다면 아직 지연 로딩에 대한 개념이 조금 부족하다 할 수 있다.

위에서 설명한 내용을 기억하는가?


지연 로딩이란 간단하게 설명을 하면 참조된 엔티티가 실제로 사용될 때 DB로의 쿼리를 통해서 가져오는 것이다.


위 코드에서는 지연로딩으로 설정된 User 엔티티가 실제로 사용되지 않는다.

위 get() 메서드를 통해서 User 엔티티를 가져왔지만, 이는 프록시 객체이다.

JPA는 지연로딩시 참조된 엔티티의 필드 값이 사용되기 전까지 해당 객체를 프록시 객체로 생성한다.

그렇기에 위 코드를 토대로 클라이언트에 Response를 하게 된다면, 그대로 에러가 나게된다. 그 이유로는 Jackson 라이브러리에서 프록시 객체를 JSON으로 직렬화를 할 수 없기 때문이다.

이러한 코드를 얕은 복사라고 칭한다.

반면 깊은 복사는 실제 엔티티의 필드의 값을 조회하는 코드를 의미한다.

@Getter
@Builder
public class PostResDTO {

    private String title;

    private User user;

    public static PostResDTO from(Post post) {
        return PostResDTO.builder()
                .title(post.getTitle())
                .user(post.getUser().getNickname())
                .build();
    }
}

위 코드를 통해서 User 엔티티의 필드값을 조회하는 getNickname() 코드가 호출될 때 DB로 쿼리를 날려 값을 가져오게 된다.

profile
Hello, World! \n

0개의 댓글