Spring Data JPA에서 양방향 영속성 전파 문제 -2

RID·2024년 5월 10일
0

배경


지난 번 포스팅에서 양방향 연관관계를 맺었을 때 발생했던 문제에 대해서 소개했었다.

Spring Data JPA에서 양방향 영속성 전파 문제

이번 포스팅에서는 왜 이런 문제가 발생했는지 살펴보자.

이슈 발생 이유


지난 번 이슈가 발생했던 코드에 대해서 살펴보자.

// 생략
    @Transactional
    override fun addLecture(courseId: Long, request: AddLectureRequest): LectureResponse {
        val course = courseRepository.findByIdOrNull(courseId) ?: throw ModelNotFoundException("Course", courseId)
        val lecture = Lecture(
            title = request.title,
            videoUrl = request.videoUrl,
            course = course
        )
        course.addLecture(lecture) 
        courseRepository.save(course) 
        return lecture.toResponse() // NPE!!!
    }
// 생략

위의 코드에서 course의 경우 이미 데이터베이스에 저장되어 있는 정보를 조회해서 객체를 가져오게 된다. 이 때 course는 예전에 생성될 때 이미 영속화가 되어있었고, 현재 준영속 상태를 가지고 있다.

반면 lecture의 경우 현재 처음 객체를 생성했고, 당연하게도 비영속 상태이다.

기대했던 바는 Cascade.ALL을 통해 영속성 전이가 일어날 때, course에 새롭게 추가된 lecture가 비영속 상태이니 이것이 영속화가 되길 기대한다.

여기까지 상황만 이해하고 추가적인 정보를 위해 JpaRepository.save()함수를 한 번 살펴보자.

SimpleJpaRepository.class


    @Transactional
    public <S extends T> S save(S entity) {
        Assert.notNull(entity, "Entity must not be null");
        if (this.entityInformation.isNew(entity)) {
            this.entityManager.persist(entity);
            return entity;
        } else {
            return this.entityManager.merge(entity);
        }
    }

여기 살펴보면 인자로 들어온 entityisNew()인 경우 즉, 비영속 상태의 객체인 경우 persist()가 호출되며 해당 객체가 그대로 반환된다. 반면 비영속 상태가 아닌 경우 merge()가 호출되고 새로운 객체가 할당되어 반환된다.

우리가 기대했던 바는 lecture에 대한 저장이 이루어질 때 persist()가 호출되길 기대했고, 결과적으로 addLecture함수 내에서 생성한 객체를 통해 반환까지 하고 싶었다.

하지만 이는 course가 이미 영속화가 되었기 때문에 courseRepository.save(course)merge()가 호출되고, cascade 조건에 의해 영속성이 전이되는 lecture에게도 merge()가 호출된 것이다.

그렇기 때문에 기대했던 바와 다르게 새로운lecture 객체를 만들어 영속화를 진행했고, addLecture내에 있는 lectureid값이 없는 상태로 남아있던 것이다.

정리


일반적인 라이브러리를 쓰는 것이 아니고 프레임워크이기 때문에 내부 동작 방식을 이해하지 못하고 클론코딩만 하면 숨은 에러를 절대 잡을 수 없을 것 같다.

repository에 있는 save()라는 함수도 맥락만 이해하면 '아 그냥 저장해주는 거구나'라고 느껴지지만 제대로 사용하려면 영속 컨텍스트의 개념을 알고 있어야한다. 이번 이슈도 함수들의 내부 동작 방식을 이해하지 못했기 때문에 원인을 찾기 어려웠고, 이슈를 해결하기 위해 올바르지 못한 방향으로 코드를 수정하기도 했었다.

늘 그렇지만 공부는 시간이 걸리더라도 이해가 우선이다. 내가 쓰는 코드가 무슨 원리로 동작하는지 최대한 이해할려고 해보자.

0개의 댓글