티스토리 뷰

kotlin + jpa 를 사용할때 가장 고민되는 것중 하나는 entity 에 class 와 data class 중 어떤걸 사용하는가에 대한 선택이다. 사실 class 를 이용할때는 아무런 이슈가 없다. data class 를 사용하고 싶다는 의문?유혹? 이 들때 이런 고민을 하게 될텐데 몇가지 우려되는 부분들을 확인해보자.

 

1. spring guide

spring guide 에서는 JPA 는 data class 가 제공하는 메서드들을 염두하고 작성되지 않았으므로 class 를 사용하라고 한다.

 

Here we don’t use data classes with val properties because JPA is not designed to work with immutable classes or the methods generated automatically by data classes. If you are using other Spring Data flavor, most of them are designed to support such constructs so you should use classes like data class User(val login: String, …​) when using Spring Data MongoDB, Spring Data JDBC, etc.

 

기존에 java 에서 JPA 를 사용할때도 toString(), equals() 메서드 구현은 신중히 했어야 한다. JPA 를 사용하다보면 순환 관계가 되는 연관 관계도 맺게되는 경우가 있는데 이때 별 생각없이 위 메서드들을 구현하면 stack over flow 가 발생하는 일이 다분하기 때문이다. 특히 entity 를 그대로 http response 로 사용하는 경우 json 직렬화 과정에서 해당 문제가 발생하는 경우도 흔한 문제다. 이 때문에 data class 를 쓰더라도 toString(), equals() 는 안전한 형태로 다시 오버라이딩을 하는게 좋을 것 같다.(이럴거면 data class 사용하는 이유가??)

 

2. entity dirty check

dirty check 는 트랜잭션 내에서 엔티티에 상태변경이 발생할 경우 save() 와 같은 repository 메서드를 직접 호출하지 않아도 영속성 컨텍스트가 update 쿼리를 발생시키는 기능이다. repository 와 entity 를 정말 컬렉션 다루듯이 다루자는 개념에서 도입된 기능이다.(list 에서 엘리먼트를 꺼낸다음 상태변경을 하면 다시 add() 를 하지않아도 된다.)

 

일단 data class 내 필드들을 변경 가능한 var 로 만들고 상태 변경을 하도록 작성하면 문제없이 dirty check 로 상태 변경을 DB 에 반영할 수 있다. 하지만 data class 내 필드를 변경 불가능한 val 로 만들고, 자동 제공되는 copy() 를 이용해서 상태 변경을 하게되면 dirty check 는 작동하지 않는다. 애초에 상태 변경이 아니라 신규 객체 생성이 되는거니 당연하다면 당연할 것이다. 이 경우엔 repository 의 save() 메서드를 통해 명시적으로 저장해야한다.

 

3. inherit

kotlin 은 class, data class 상관없이 기본적으로 상속이 불가능하다. 상속을 하기위해서는 open 키워드를 붙여줘야 한다. 하지만 data class 에는 open 을 붙일수가 없다. kotlin 언어스펙이다. 그럼 상속이 필요없는 경우엔 data class 를 쓰면 될까? JPA 는 자바 프레임워크답게 프레임워크 내에서 적극적으로 상속을 사용한다. 개발자가 명시적으로 상속을 사용하지 않더라도 내부 lazy loading 을 구현하기 위해 상속을 이용해 proxy 객체를 만들게 된다. 때문에 상속이 막혀있으면 JPA 는 proxy 객체를 만들지못하고, eager loading 으로 동작하게 된다. 이는 엔터프라이즈 환경에서는 치명적인 성능 이슈를 불러올 수 있다. 다만 이 이슈를 해결하기 위해서는 class 를 사용하더라도 일일이 open 을 붙여줘야하는 불편함이 따른다. 이를 해소하기 위해 jetbrains 는 allopen 이라는 gradle plugin 을 제공하고 있으니, 개발자가 일일이 open 을 붙여줄 필요는 없다.

 

다만 여기서 알고 넘어가야할 점은 allopen 은 data class 에도 open 을 붙인다는 점이다. 꽤나 흥미로운 부분인데 아마도 allopen 이 코틀린 컴파일러 수준의 플러그인이다보니 class, data class 구분없이 상속가능하게 만들 수 있는 것으로 보인다. 이런 현상을 일부러 의도한것 같지는 않으나 jetbrains 에서도 인지는 하고 있는 것으로 보이며, data class 의도에 맞게 data class 에는 사용하지 말기를 권고하고 있다. 이와 관련해서는 이전에 포스팅을 작성한 적이 있다. 여튼 상속을 이용해 프록시를 만드는 관점에서는 class, data class 모두 이슈가 없다.

 

# 정리

몇가지 이슈가 될 부분들을 체크해보면서 kotlin+JPA 환경에서 data class 를 써도 될지에 대해 알아봤다. 이를 정리해보자.

 

장점

- 엔티티를 불변으로 사용하고 싶다면 copy() 를 사용할 수 있음

 

단점

- toString(), equals() 필수 오버라이딩. class 를 사용할때도 마찬가지이나 누락했을때 피해가 data class 가 압도적으로 큼

- spring guide 에서 class 권고하는걸 무시해야 함(다만 kotlin 이나 JPA 권고가 아닌만큼 꼭 따를 필요는 없다고 볼 수도 있어보임)

- data class 를 이용하겠다면 copy() 를 필요로 하는 경우일텐데 copy() 를 이용하면 dirty check 를 사용하지 못함

- kotlin 에서 정의한 data class 스펙을 무시함(gradle plugin 으로 인한 상속가능)

 

이정도로 정리할 수 있을 것 같다. 결국 data class 를 사용함으로써 얻는 이점은 copy() 메서드뿐이라고 생각이 드는데 copy() 자체가 JPA 랑 맞지 않는 것 같다.(사실 kotlin 자체가...) 조건들에 대해 확인을 해보면서 느낀 점은 그냥 class 쓰자 이다.

댓글
댓글쓰기 폼