앞선 글에서, Spring boot 에서 Proxy 를 생성할 때에는 상속을 이용한다는 것을 알았습니다.
여기서 코틀린과의 문제가 발생합니다.
1. Data Class
- Data Class 는 코틀린에 있는 특별한 클래스입니다.
- 다음의 메서드들을 적절한 방법으로 구현하여 자동으로 생성해줍니다.
- equals()
- hashCode()
- toString()
- copy()
이 메서드들은 최상위클래스인 Object 클래스에 정의되어, 굳이 Data Class 가 아니더라도 사용할 수 있죠.
그럼 Data Class 가 재정의해주는 위 메서드들은 어떤 차이가 있을까요 ?
먼저 그냥 class 로 정의한 경우입니다.
두번째로는 data class 로 정의한 경우입니다.
정리해보면 다음과 같습니다.
- equlas() : 참조가 아니라 데이터 클래스 간 값의 일치를 비교한다.
- hashCode() : equals() 가 참을 반환하는 두 인스턴스는 같은 hashCode()를 가지게 해준다.
- toString() : 데이터 클래스는 프로퍼티의 값을 나열하게 오버라이딩한다.
값객체를 만들어준다고 할 수 있겠네요.
이런 이유로, entity 객체를 data 클래스를 만들어 사용하는 경우가 있습니다.
하지만 entity 클래스를 data class 로 만들면 문제가 발생합니다.
2. 문제점
entity 클래스를 data class 로 사용했을 때, toString() 에서 순환참조가 발생할 수 있는 문제도 있지만 이 글에서는 다른 문제점을 다룹니다.
코틀린에서 class 를 만들면, 자바 코드로 변환될때 자동으로 final 클래스로 만들어집니다.
final 클래스는 상속이 불가능하죠.
그래서 코틀린으로 상속이 가능한 클래스를 만드려면, open 이라는 키워드를 클래스 앞에 붙여야 합니다.
entity를 data class 로 만들었는데, 상속이 가능하게 만들고 싶다?
그럼 아래처럼 하면 될까요 ?
보시다시피, 에러가 납니다.
코틀린 공식 레퍼런스를 보면 아래와 같은 글귀가 있습니다.
Data classes cannot be abstract, open, sealed, or inner.
https://kotlinlang.org/docs/data-classes.html
data class 는 open 이 될 수 없다는군요.
3. Data Class 를 상속할 이유가 있어?
있습니다.
앞 글에서도 언급했듯이,
- Entity 를 만들고 싶은데,
- Data Class 로 만들었을때.
Hibernate Entity 에서 연관관계가 있는 객체를 만들 때, 우리는 Lazy 옵션을 많이 사용합니다.
@OneToMany(fetch = FetchType.LAZY)
복잡한 연관관계를 가진 entity 객체를 생성할 때, 당장 필요없는 데이터를 로딩하지 않아 성능 향상을 바랄 수 있죠.
이렇게 fetchType 이 Lazy 인 entity 를 로딩할 때, 처음에는 proxy 객체로 만들어 빈 껍데기를 놔두고, 실제로 사용할 때 쿼리를 해와서 데이터를 채우게 됩니다.
이제 결론이 나왔습니다.
- 우리는 Entity 에서 Lazy loading 을 하게 될 것입니다.
- Lazy loading 은 proxy 를 사용합니다.
- proxy 는 상속을 사용합니다.
data class 는 open 이 되지 않아서 상속 할 수 없는데 ???
proxy 객체를 못만들겠군요.
4. 한번 실제로 보자.
연관관계를 가진 두 클래스를 정의하였고,
데이터를 넣어 줍시다.
INSERT INTO library (name) VALUES('별빛 도서관');
INSERT INTO book (name, library_id) VALUES ('Effective Java 3E', '1');
그럼 테스트해 봅시다.
정상적인 Lazy 가 먹힌 코드라면, book 을 repositoy 에서 조회해온 시점에는, Library 가 빈 proxy 객체여야 할 것입니다.
하지만,
그냥 객체가 들어있군요.
-> final 인 data class 를 상속하지 못해, proxy 를 생성하지 못한 모습입니다.
-> Lazy 라고 명시했음에도 불구하고, proxy 를 사용하지 못합니다.
5. 어떡해야 하는데
간단합니다.
- data class 를 사용하지 않고, 일반 class 를 사용합니다.
- 이후 class 앞에 open 을 붙여줘서 상속이 가능하게 합니다.
매 entity 클래스마다 open 을 붙여주면야 되지만, 이는 너무 귀찮기에 대부분 사용하는 플러그인이 있습니다.
all-open plugin
- open 키워드를 자동으로 붙여주는 플러그인입니다.
- build.gradle 에 다음과 같이 정의합니다.
-
plugins { ... kotlin("plugin.allopen") version "1.3.71" } allOpen { annotation("javax.persistence.Entity") }
- Entity 클래스에는 자동으로 open 을 붙여주는 클래스입니다.
이제 다시 시도해 봅시다.
플러그인도 적용하였고, data 키워드도 땠습니다.
이제는 프록시가 잘 들어왔군요.
결론 : entity 클래스에서 Lazy Loading 을 사용하고 싶다면, data class 를 사용하면 안된다.
6. 혼종 코드.
만약 allOpen 플러그인은 설정하고, data 키워드를 때는걸 깜빡하면 어떻게 될까요.
data 클래스에 open 이라는 키워드를 명시적으로 붙이는 건 컴파일에러였지만, 위의 상황은 컴파일 에러가 나지 않습니다.
또한 자바코드로 컴파일해보면 final 이 붙지 않은 즉, open 이 적용된 것 처럼 작동합니다.
따라서 프록시 객체도 잘 만들어집니다.
data class 로 만들어 놓고, allOpen 플러그인으로 강제로 open 으로 만든 결과
뭐, 그럼 data class 로도 Proxy 만들 수 있네 ! 라고 할 수 있지만,
Kotlin 에서 data class 는 open 으로 만들 수 없다는데, 이렇게 사용하고 싶진 않습니다.
7. 다음번에 알아 볼 것.
이번 글은 코틀린 공부를 처음 할 때 https://techblog.woowahan.com/2675/ 우아한 형제들 블로그에서 참고한 내용이 바탕이 되었습니다.
이 블로그에서 일반 클래스를 사용할때,
- toString(), equals(), hashcode() 구현은 kassava 라이브러리를 사용하여 구현할 수 있습니다.
라고 소개하고 있으니 팁이 될 수 있을 것 같습니다.
하지만 조금 다른점이 있습니다. no-args 플러그인은 저는 사용하지 않았는데, 잘 되었습니다.
다음 글에서는 이점을 다루어 보겠습니다.
'Kotlin' 카테고리의 다른 글
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (6) (0) | 2021.09.30 |
---|---|
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (5) (1) | 2021.09.07 |
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (4) (0) | 2021.09.06 |
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (3) (0) | 2021.09.06 |
코틀린과 Hibernate, CGLIB, Proxy 오해와 재대로된 사용법 - (1) (0) | 2021.09.06 |