2022-08-16 @이영훈
Kotlin Spring에서 abstract class의 Entity를 설정하면 다음과 같은 에러를 만납니다.
org.hibernate.HibernateException: Getter methods of lazy classes cannot be final: me.leedo.application.work.domain.Item#getId
위 에러를 알아보고 이슈를 해결한 과정을 기록으로 남깁니다.
문제 상황
다음과 같이 상속 전략을 SINGLE_TABLE로 하는 부모 클래스를 abstract class로 생성합니다.
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
abstract class Item(
@Id
@Column(name = "item_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val name: String,
)
Kotlin
복사
Spring을 실행하면 다음과 같이 에러가 나타납니다
org.hibernate.HibernateException: Getter methods of lazy classes cannot be final: me.leedo.application.work.domain.Item#getId
hibernate에서 lazy 클래스의 getter 메소드는 final일 수 없다라는 에러입니다.
에러 해결 과정
abstract class인 Item의 모든 맴버 변수를 open으로 열면 됩니다. Kotlin에서는 모든 클래스와 맴버 변수는 기본적으로 final 입니다.
다음과 같이 AllOpen 어노테이션을 생성합니다.
package me.leedo.application.shared
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class AllOpen
Kotlin
복사
build.gradle.kts 파일에서 allOpen을 설정합니다
plugins {
val kotlinVersion = "1.7.10"
// plugin.spring은 allOpen 플러그인과 스프링과 통합하기 위해 allOpen 플러그인에 추가적인 설정을 넣은 플러그인 입니다
kotlin("plugin.spring") version kotlinVersion
}
// 위에서 생성한 AllOpen 어노테이션을 추가합니다
allOpen {
annotation("me.leedo.application.shared.AllOpen")
}
Kotlin
복사
Item 클래스에서 @AllOpen 어노테이션을 붙입니다
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@AllOpen
abstract class Item(
@Id
@Column(name = "item_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val name: String,
)
Kotlin
복사
kotlin 바이트코드를 decompile 해보면 46번째 줄과 같이 final이 없어진 것을 확인할 수 있습니다.
그리고 스프링을 실행해도 에러가 뜨지 않습니다
여기까지만해도 문제가 해결이 됩니다.
저는 실무 코드에서는 다음과 같이 해결하였습니다.
BaseEntity에 @AllOpen 설정
보통 실무에서는 BaseTimeEntity나 BaseEntity 클래스를 두고 상속 받아서 많이 구현합니다.
BaseTimeEntity에 @AllOpen 을 적용하고 이것을 상속받아서 해결하였습니다.
다음과 같이 BaseTimeEntity 클래스가 있습니다.
@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
@AllOpen // @AllOpen 어노테이션을 적용하였습니다
abstract class BaseTimeEntity {
lateinit var createdAt: OffsetDateTime
lateinit var updatedAt: OffsetDateTime
@PrePersist
fun prePersist() {
createdAt = OffsetDateTime.now()
updatedAt = OffsetDateTime.now()
}
@PreUpdate
fun preUpdate() {
updatedAt = OffsetDateTime.now()
}
}
Kotlin
복사
Item 클래스를 BaseTimeEntity 클래스를 상속받아 구현합니다
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
abstract class Item(
@Id
@Column(name = "item_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val name: String,
) : BaseTimeEntity()
Kotlin
복사
Kotlin 파일을 decompile해서 확인해보면 getter 메소드 모두 open 되어 있음을 확인할 수 있습니다.