티스토리 뷰

Kotlin

Nullable value class: Long? long?

nodeal 2023. 5. 7. 00:41
반응형

JPA를 이용하거든 그렇지 않든 object의 ID를 Java primitive type으로만 관리하는 것은 여간 귀찮은 일이 아니다. ID를 사용한 조회에서 Long만 여러개 전달하는 상황은 충분한 문서화가 이뤄지지 않은 상황에서 어떤 의도하지 않은 상황으로 이어질지 모른다. 그래서 Kotlin value class를 사용하여 ID를 wrap하여 Java에서는 unwrap된 타입으로 사용되길 기대하고 사용하는데...

 

다음 예시를 보자. JPA Entity에 value class ID를 사용한 모습이다.

FooEntity.kt

여기까진 아무런 문제가 없다. JpaRepository를 사용하더라도 ID에 unwrap된 type을 넣어준다면 findById까지 완벽히 작동한다. 이때, table `foo`를 참조하는 table `bar`가 있고 이게 optional할 때를 가정하고 BarEntity를 작성해보자.

BarEntity.kt

코드에서 표시하듯 FooId가 nullable일 때 Basic 타입 추론을 실패하여 오류로 취급한다. Inspection 수준에서 처리할 수 있도록 해주는 것이다. 여기까지만 보면 적당히 Entity에서는 primitive type으로 사용하면 될 것 같아보인다. 하지만 FooId가 IdWrapper를 상속받는 클래스였다면 어떻게 될까?

FooEntity.kt: IdWrapper를 상속받은 FooId
BarEntity.kt: FooId의 오류가 사라졌다.

여기서 문제가 발생한다. 어차피 대부분의 경우에서 IdWrapper같은 interface를 활용하여 정렬같은 extension을 잔뜩 넣어줄 생각으로 구조를 만들었을텐데 FooId가 아무런 interface도 상속받지 않는다는 것이 오히려 어색하다. 하지만 이렇게 다른 interface를 상속받는 것만으로 inspection의 오류는 사라졌다. 정말 오류가 해결되었을까?

Test.kt: 실행 결과

Table `bar`의 insert SQL 2번 parameter가 FooId로 전달된다. 따라서 hibernate가 VARBINARY로 인식하고 적어도 BIGINT는 아닐 것 같은 값을 삽입하려고 시도한다. 왜 이런 오류가 발생할까? 예상할 수 있듯 FooId가 unwrap되지 않은 것이다.

Decompile된 BarEntity

fooId를 FooId로 가지고 있다. 이 때문에 JPA는 FooId라는 클래스로 인식하고 객체를 통째로 넣는 시도를 하는 것이다. value class는 분명 unwrap되어야 할텐데 Java primitive type에는 null을 사용할 수 없다는 문제에서 기인하다.

value class는 unboxing 과정에서 primitive, nullable일 때 class boxing이 작동하지 않는 것을 확인할 수 있다. 적어도 Long?은 @Nullable java.lang.Long으로 변환되는 것을 보면 언젠가 해결될 수 있는 문제라고 기대된다. 그럼에도 Kotlin 수준에서 해결되기 전까지는 불편하더라도 다른 방법을 사용해야 할 것 같다.

 

1. Entity에는 value class를 사용하지 않는다.

이걸 모두 지키기는 어려우니 적어도 nullable에는 value class를 적용하지 않는다. 대부분의 경우에서 Entity는 형식을 정의하는 다른 interface를 상속받고 이 interface에서 value class를 사용할 것이다. 그렇다면 interface를 상속받았을 때 부모 타입을 만족하며 작성하는 방법이 필요할 것이다.

BarEntity.kt: primitiveFooId

primitiveFooId로 JPA에 매핑한 다음 직렬화를 막기 위해 Transient annotation을 달았다. 이전처럼 직렬화되면 fooId가 들어갈 것이다.

 

2. 직접 UserType을 작성

어렵고 험한 길이 될 것이다. JPA는 기본 타입 이외의 형식을 지원하기 위해 UserType을 상속받은 클래스를 사용하여 직접 작성할 수 있도록 한다. 하지만 유일한 concrete type일 때에는 충분히 가능성이 있으나 IdWrapper같은 interface를 사용하여 전체를 매핑할 방법은 없다. 번거롭더라도 각 ID에 대해 등록해준다면 Entity 수준에서 다른 처리없이 값이 대응될 것이다.

 

3. Entity는 그냥 Java로 작성하자.

반응형

'Kotlin' 카테고리의 다른 글

Ktor Pipeline, PipelinePhase  (0) 2023.08.26
Kotlin/JVM Vector, ArrayList, Mutex 수행 시간  (0) 2023.02.11
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함