24-05-29 Spring 팀 프로젝트 (2)
오늘도 뉴스피드 팀 과제를 팀원들과 진행하면서 하루의 대부분의 시간을 보냈던 것 같다. 쉬워보였던 것들도 여러가지 문제에 부딪히게 되었고, 간단해 보이는 기능도 많은 고민이 필요하다는 것을 깨달을 수 있었다.
너무 욕심부리지 말고 할 수 있는 범위 내에서 최선을 다해보도록 노력해보아야겠다. 모르는 건 더 배우면 될 것이라고 생각한다.
여기에는 오늘 프로젝트 진행중에 발생했던 문제에 대해 간단하게 적어보았다.
1. LazyInitializationException 예외 발생
오늘 팀 프로젝트를 진행하다보니 LazyInitializationException 이라는 예외가 발생했었다. 왜 이런 문제가 발생했는지 어떻게 해결했는지 간단하게 적어본다.
포스트 단 건 조회를 했을 때 위와 같은 예외가 발생했었다. 문제가 발생한 부분부터 살펴보자
fun getPostById(postId: Long): PostWithCommentResponse {
val post: Post = postRepository.findByIdOrNull(postId) ?: throw ModelNotFoundException("Post", postId)
return post.toWithCommentResponse()
}
위와 같이 단순히 포스트를 조회해와서 Response DTO로 변환해서 내보내는 과정에서 예외가 발생한 것이다.
왜 이런 예외가 발생했는지 알려면 엔티티의 구조부터 알아야한다. Comment와 Post는 양방향 연관관계를 맺고있고 연관관계의 주인은 Comment이다. Post쪽이 일대다 연관관계를 맺고있다.
지연 초기화 예외라는 이름만 가지고도 어느정도 위와 같은 예외가 발생하는 이유를 유추할 수 있다. Post 아래에 달려있는 Comment들의 값을 DB로부터 가져오지 못해 초기화에 실패해서 예외가 발생했을 것이다.
다시 코드를 살펴보니 Comment들을 가져올 때 fetch 타입을 따로 지정해주지 않았다는 것을 알 수 있었다.
@OneToMany(mappedBy = "post")
var comments: MutableList<Comment> = mutableListOf()
fetch 타입을 지정해주지 않으면 JPA는 어떻게 작동하는지 찾아보았다.
따로 설정하지 않는 경우 기본 설정은 다음과 같다.
- @ManyToOne, @OneToOne : 즉시 로딩
- @OneToMany, @ManyToMany: 지연 로딩
우리는 @OneToMany이기 때문에 지연 로딩이 기본 설정으로 되어 있었고, Comment들을 가져올 때 프록시 객체만 들어있는 상태였던 것이다.
해결하기 위한 방법은 크게 두 가지가 있을 것 같다.
첫 번째 방법은 fetch 타입을 즉시 로딩(EAGER)으로 바꿔주는 것이다. 그러면 처음 DB에서 Post를 조회하면서 연관있는 Comment들을 모두 같이 조회해서 가져와서 사용할 수 있게 될 것이다.
두 번째 방법은 함수에 @Transactional 어노테이션을 붙여주는 것이다. 프록시 객체만 가져온 상태에서 트랜잭션이 종료되어버렸기 때문에 실제 Comment들에 담겨있는 내용물들을 이용할 수 없어서 오류가 발생했었다. 따라서 @Transactional을 붙여서 영속성 컨텍스트를 유지시켜주면 실제 Comment 안에 있는 정보를 이용하려고 할 때 프록시 객체가 초기화되면서 DB에서 값을 조회해올 수 있을 것이다.
여기서 택한 방법은 @Transactional을 붙이는 것이었다. 왜냐하면 피드를 띄울 때는 Comment 들을 모두 가져올 필요가 없기 때문이다. Post를 단건 조회할 때는 그 아래에 달려있는 댓글들을 보여주면 되지만, Post 목록을 조회할 때는 댓글들의 내용들을 모두 가져오는 건 불필요하다.
2. 오늘 배운 것
- 협업 과정의 어려움을 프로젝트를 진행하면서 많이 느낄 수 있었다. 시행착오를 겪다보면 나아질거라 생각한다.
- 프록시 객체나 지연 로딩, 즉시 로딩에 대해 조금 더 자세히 알게 되었다.