Spring Data JPA - Kotlin + JPA에서 조심해야할 부분

Spring Data JPA

백기선님의 강의인 Spring Data JPA 강의를 듣고 공부한 내용을 정리한 글

Kotlin + JPA에서 조심해야할 부분

백기선님의 인프런 JPA 강의를 코틀린으로 따라해보던 중 @OneToMany 애노테이션에서 다음과 같은 오류가 발생했다.

스크린샷 2020-04-27 오후 9 53 54

아래는 해당 클래스 관계이다.

@Entity
data class Study (
    @Id @GeneratedValue
    var id:Long?,
    val name: String,

    @ManyToOne
    val owner:Account
)
@Entity
data class Account(
    @Id @GeneratedValue
    val id:Long?,
    // @Column이 생략되어 있음
    val username:String,
    val password:String,

    @Embedded
    // kotlin에선 AttributeOverrides 안써도 됨
    @AttributeOverride(name = "street", column = Column(name = "home_street"))
    val address:Address?,

    @OneToMany
    val studies:MutableSet<Study> = HashSet()
)

이유를 몰라서 에러 로그를 찬찬히 살펴보니 바로 data class 때문이었다.

data class는 코틀린 클래스에있는 모든 프로퍼티에 대한 hashCode(), toString(), equals()를 자동으로 만들어주는 아주 편리한 기능이지만 JPA와 조합해서 사용할 때는 조심해야한다.

사건의 전말은 이렇다.

결론부터 이야기하자면 data class에서 자동으로 생성되는 toString()이 문제의 원인이었다.

문제의 시나리오는 다음과 같이 진행된다.

  1. Study data class가 toString을 만들기 위해 프로퍼티인 Account의 toString 메소드를 호출

  2. Account의 studies 역시 제네릭 타입이 Study이므로 Study의 toString을 호출

  3. Study의 toString에서 Account의 toString을 호출…

  4. 무한 반복

이 과정이 무한히 반복되다가 펑~ 하고 StackOverflowException 발생하게 된 것이다.

이 경우를 회피하기 위해서는 간단한 두 가지 솔루션이 존재한다.

첫 번째는 주 생성자에 있는 프로퍼티를 바깥으로 빼내는 방법이 있다.

아래와 같이 가능하다.

@Entity
data class Study (
    @Id @GeneratedValue
    var id:Long?,
    val name: String,
){
    @ManyToOne
    val owner:Account
}
@Entity
data class Account(
    @Id @GeneratedValue
    val id:Long?,
    // @Column이 생략되어 있음
    val username:String,
    val password:String,

    @Embedded
    // kotlin에선 AttributeOverrides 안써도 됨
    @AttributeOverride(name = "street", column = Column(name = "home_street"))
    val address:Address?,
){
    @OneToMany
    val studies:MutableSet<Study> = HashSet()
}

이렇게 본문에 선언해두면 data class의 자동 완성 타겟이 되지 않는다.

두 번째 방법으로는 아래와 같이 toString()을 명시적으로 오버라이딩해서 참조 클래스를 호출하지 않는 방법이 있다.

@Entity
data class Study (
    @Id @GeneratedValue
    var id:Long?,
    val name: String,
    @ManyToOne
    val owner:Account,

    override fun toString(): String{
        return "Study(id=$id, name='$name', owner=${owner.name})"
    }
)

원론적인 해결책

사실 가장 좋은 방법은 data class를 안쓰면 된다. (두둥)

하이버네이트와 data class가 여러모로 궁합이 안맞아서 가급적이면 JPA 쓸 때는 data class를 안쓰는게 좋다고 한다.

Reference

인프런 백기선님의 스프링 Data JPA

https://stackoverflow.com/questions/54057136/stackoverflowerror-while-using-manytomany

https://roamingthings.de/posts/kotlin-data-class-in-bi-directional-jpa-relations/



© 2022. by minkuk

Powered by minkuk