Data Class, Class
Hexagonal Architecture ํจํด์ ์ ์ฉํ ๋, ๋๋ฉ์ธ ๋ชจ๋ธ๊ณผ ์์ฒญ/์๋ต DTO๋ Data Class๋ฅผ ์ฌ์ฉํ๋ค. ๋ฌผ๋ก ์ผ๋ฐ์ ์ธ Kotlin Class๋ฅผ ์ด์ฉํ ์๋ ์์ง๋ง, Boilerplate๋ฅผ ์ปดํ์ผ๋ฌ ์์น์์ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์๊ธฐ ๋๋ฌธ์ Data Class๊ฐ ์์ฃผ ์ฌ์ฉ๋๋ค.
ํ์ง๋ง JPA๋ฅผ ์ฌ์ฉํ๋ ํ๊ฒฝ์์, Persistence Layer์ Entity Class๋ Data Class๋ก ์ ์ธํ ์ ์๋ค. ์ ๊ทธ๋ด๊น?
Reason #1. Proxy
JPA(Hibernate)๋ ์ฑ๋ฅ์ ์ํ์ฌ ์์ฃผ ์๋ฆฌํ๊ฒ ๋์ํ๋ค. ๋ง์ฝ ์ฃผ๋ฌธ(Order) ์ ๋ณด๋ฅผ ์กฐํํ๋ ค๊ณ ํ ๋, ์ฃผ๋ฌธํ ์ฌ์ฉ์(User) ๋ฐ์ดํฐ๋ ๋น์ฅ ํ์ํ์ง ์์ ๊ฒ์ด๋ค. ์ด๋, Hibernate๋ ์ผ๋จ User ์๋ฆฌ์๋ ๊ฐ์ง ๊ฐ์ฒด(Proxy)๋ฅผ ๋ฃ์ด๋๊ณ , ๋์ค์ ์ง์ง ํ์ํ๋ฉด ๊ทธ๋ DB์์ ๊ฐ์ ธ์จ๋ค. ์ง์ฐ ๋ก๋ฉ(Lazy Loading) ๋ฐฉ์์ด๋ค.
Proxy Class๋ ์๋ณธ Class๋ฅผ ์์๋ฐ์์ ํ๋๊ฐ์ ๋น์ด ์๋ ๊ป๋ฐ๊ธฐ๋ง ๊ฐ์ ์์ Class๋ก ๋ง๋ค์ด์ง๋ค. ํ์ง๋ง Kotlin์ Data Class๋ ๊ธฐ๋ณธ์ ์ผ๋ก final Class, ์ฆ ์์์ด ๋ถ๊ฐ๋ฅํ Class์ด๋ค.
์ด๋ ๊ฒ ๋๋ฉด, Proxy Class๊ฐ ์์ฑ๋์ง ์๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ๋ก๋ฉ(Eager Loading)์ ํตํ์ฌ ๊ฐ์ ธ์ค๊ฒ ๋๋ค. ๋ง์ฝ ๋ฐ์ดํฐ๊ฐ ๋ง์์ง ๊ฒฝ์ฐ, ์ด๋ ์์ฒญ๋ ์ฑ๋ฅ ๋ฌธ์ ๋ก ์ด์ด์ง ์ ์๋ค.
Reason #2. Hashcode
Kotlin Data Class๋ ์์ฑ์์ ์ ์๋ ๋ชจ๋ ํ๋๋ฅผ ์ฌ์ฉํ์ฌ hashcode()๋ฅผ ์๋ ์์ฑํ๋ค.
๋ง์ฝ ๋ Entity A, B๊ฐ ์๋ก๋ฅผ ์ฐธ์กฐํ๊ณ ์๋ค๋ฉด, ๋ง์ฝ a.hashcode()์ด ํธ์ถ๋์์ ๋, A์ ํ๋์ ์ํด์๋ B์ Hashcode๋ฅผ ์ป๊ธฐ ์ํ์ฌ ๋ค์ b.hashcode()๊ฐ ํธ์ถ๋๊ณ , B์ ํ๋์ ์ํด์๋ A์ Hashcode๋ฅผ ์ป๊ธฐ ์ํ์ฌ ๋๋ค์ a.hashcode()๊ฐ ํธ์ถ๋๋, ๊ทธ์ผ๋ง๋ก ๋ฌดํ๋ฃจํ๊ฐ ์์ฑ๋์ด StackOverFlowError๊ฐ ๋ฐ์ํ ์ ์๋ค.
Reason #3. Identity vs State
JPA์์๋ Entity๋ค์ HashSet์ ๋ด์์ ๊ด๋ฆฌํ๋ค.
๋ง์ฝ Data Class๋ฅผ ์ฌ์ฉํ์ฌ Entity๋ฅผ ๋ง๋ค์๋ค๊ณ ๊ฐ์ ํ์. ํด๋น Entity ๊ฐ์ฒด๋ฅผ ํ๋ ์์ฑํ๋ฉด, Data Class๋ ๋ชจ๋ ํ๋๋ฅผ ์์ด์ hashCode()๋ฅผ ๋ง๋ค๊ณ , ์ด ๊ฐ์ ๋ฐ๋ผ HashSet์ ํน์ ์์น(Bucket)์ ๋ด๊ธฐ๊ฒ ๋๋ค.
์ดํ ๋น์ฆ๋์ค ๋ก์ง์์ user.name = "James"์ ๊ฐ์ด ํ๋๊ฐ์ ๋ณ๊ฒฝํ๊ฒ ๋๋ฉด ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. ์ฒซ์งธ, hashCode๊ฐ ๋ณํ๋ค. Data Class๋ ๋ณ๊ฒฝ๋ ํ๋๊น์ง ํฌํจํด ํด์์ฝ๋๋ฅผ ์ฌ๊ณ์ฐํ๋ฏ๋ก, ์ด ๊ฐ์ฒด๋ ์๋ฑํ ํด์๊ฐ์ ๊ฐ์ง๊ฒ ๋๋ค. ์ฆ, HashSet์ ์ด ๊ฐ์ฒด๋ฅผ ์๋ฑํ ๋ฐฉ(Bucket)์์ ์ฐพ์ผ๋ ค ์๋ํ๊ฒ ๋๋ค.
๋์งธ, equals()๋ง์ ์คํจํ๋ค. ์ค๋ น ์ฐ์ฐํ ํด์๊ฐ์ด ๊ฐ๊ฑฐ๋ ์ ์ฒด๋ฅผ ํ์ํ๋ค ํด๋, Data Class์ equals()๋ โ๋ชจ๋ ํ๋ ๊ฐ์ด ๊ฐ์์ผ ๊ฐ์ ๊ฐ์ฒดโ๋ผ๊ณ ํ๋จ(State Comparison)ํ๋ค. JPA๋ โID(PK)๊ฐ ๊ฐ์ผ๋ฉด ๊ฐ์ ๊ฐ์ฒดโ๋ผ๊ณ ์ธ์(Identity Comparison)ํด์ผ ํ๋๋ฐ, Data Class๋ ๋ด์ฉ๋ฌผ์ด ๋ฐ๋์์ผ๋ โ๋ค๋ฅธ ๊ฐ์ฒดโ๋ผ๊ณ ํ๊ฒฐ์ ๋ด๋ ค๋ฒ๋ฆฌ๋ ๊ฒ์ด๋ค.
๊ฒฐ๊ตญ, ํ๋ ๊ฐ ํ๋๋ง ๋ฐ๊ฟ๋ ์ด ๊ฐ์ฒด๋ ์์ ์ฐพ์ ์ ์๊ฒ ๋๋ค. ๊ฐ์ฒด๋ฅผ ์ญ์ ํ๋ ค ํด๋ remove(user)๊ฐ ๋์ํ์ง ์๋๋ค.
์ด๋ ๋ง์น Dangling Pointer์ ๋ฐ๋๋๋ ๋ฌธ์ ์ ๊ฐ๋ค. Dangling Pointer๋ ํฌ์ธํฐ๋ ์๋๋ฐ ์ค์ฒด(๋ฉ๋ชจ๋ฆฌ)๊ฐ ์ฌ๋ผ์ง ๊ฒ์ด๋ผ๋ฉด, ์ด ๋ฌธ์ ๋ ๊ฐ์ฒด๋ Heap ๋ฉ๋ชจ๋ฆฌ์ ์์ฃผ ๊ฑด๊ฐํ๊ฒ ์ด์์์ผ๋, HashSet์ด โ๊ทธ๋ฐ ์ฌ๋ ์๋๋ฐ์?โ๋ผ๋ฉฐ ์ ๊ทผ์ ๊ฑฐ๋ถํ์ฌ Garbage Collection ๋์์์๋ ์ ์ธ๋๊ณ ์์ํ ๋ฉ๋ชจ๋ฆฌ ๊ณต๊ฐ๋ง ์ฐจ์งํ๋ ์ ๋ น(Memory Leak)์ด ๋๋ ๊ฒ์ด๋ค.
Persistence Context
์ด์ฒ๋ผ ๋ ๊ฐ์ง ์ธก๋ฉด์์ ํฐ ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์๋ค.
JPA๋ DB์์ ๋ฐ์ดํฐ๋ฅผ ๊บผ๋ผ ๋, ๋ฐ๋ก ์ฃผ์ง ์๊ณ Persistence Context๋ผ๋ 1์ฐจ ์บ์์ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ๋ค. ๋๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ ๋ฌ๋ผ๊ณ ํ๋ฉด ๋ค์ DB ์ ๊ทผ๊น์ง ๊ฐ์ง ์๊ณ ์บ์ฑํ ์ ์๋ ํจ์จ์ฑ์ ์ํจ์ด๋ค.
๋ํ Persistence Context๊ฐ ๋ด๋ถ์ ์ผ๋ก Entity๋ฅผ ๊ด๋ฆฌํ ๋, hashCode()์ equals()๊ฐ ๋ฏธ์น๋ฏ์ด ์ฌ์ฉ๋๋ค. ๋ฐ๋ผ์ Entity์์์ Data Class ์ฌ์ฉ์ โPersistence Context์์์ ์ค๋ฅ๋ฅผ ์ผ๊ธฐโํ๋ค๊ณ ํ๋ ๊ฒ์ด๋ค.
all-open Plugin
๋ง์ฝ data class๋ก Entity๋ฅผ ๋ง๋ค์๋ค๋ฉด ์ปดํ์ผ ๋จ๊ณ์์๋ ์๋ฌด๋ฐ ๋ฌธ์ ์์ด ์งํ๋ ๊ฒ์ด๋ค. ํ์ง๋ง ๋ฐํ์ ์ค์ Hibernate๊ฐ ํ๋ก์๋ฅผ ๋ง๋ค๋ ค๋ค๊ฐ ์คํจํ๊ณ , ์๋ฒ๊ฐ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ด๋ฟ์ผ๋ฉฐ ์ฃฝ์ ๊ฒ์ด๋ค.
๊ทธ๋ฐ๋ฐ Spring Boot ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด์ ํ
์คํธํด๋ณด๋ฉด ์๋ฌด๋ฆฌ data class๋ก Entity๋ฅผ ๋ง๋ค์ด๋ ์๋ฒ๊ฐ ๋ค์ด๋์ง ์๋ ํ์์ ๋ชฉ๊ฒฉํ ๊ฒ์ด๋ค. ๋์ ๊ณผ์ ์ ๋ํ์ฌ ์ข ๋ ์์ธํ ์ดํด๋ณด์.
build.gradle์ ์ด๋ฐ ์ค์ ์ด ์๋์ผ๋ก ๋ค์ด๊ฐ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
plugins {
kotlin("plugin.spring") version "1.x.x" // all-open ํฌํจ
kotlin("plugin.jpa") version "1.x.x" // no-arg ํฌํจ
}์ด all-open ํ๋ฌ๊ทธ์ธ(Kotlin์์๋ kotlin-spring)์ด ๋ฌด์จ ์ง์ ํ๋๋ฉด, ์ปดํ์ผ ๊ณผ์ ์์ @Entity๊ฐ ๋ถ์ ํด๋์ค๋ฅผ ๋ฐ๊ฒฌํ๋ฉด ๊ฐ์ ๋ก open์ ๋ถ์ฌ๋ฒ๋ฆฐ๋ค. ์ฌ์ง์ด ๊ทธ๊ฒ data class์ผ์ง๋ผ๋ ๋ฐ์ดํธ์ฝ๋ ๋ ๋ฒจ์์ ์ด์ด๋ฒ๋ฆฐ๋ค.
- ์์ค ์ฝ๋:
data class User(์ฝ๋ ์์์๋final) - (ํ๋ฌ๊ทธ์ธ ๊ฐ์
) โ
final์ญ์ - ์ปดํ์ผ๋ ๊ฒฐ๊ณผ๋ฌผ:
public open class User(๋ฐ์ดํธ์ฝ๋๋open)
๊ฒฐ๊ณผ์ ์ผ๋ก, Hibernate๋ โ์ด ํด๋์ค open์ด๋ค? ์์๋ฐ์์ ํ๋ก์ ๋ง๋ค์ด์ผ์ง!โ ํ๊ณ ์ ๋๊ฒ ํ๋ก์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
์? ๊ทผ๋ฐ Koltin Class๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋
final์ด์ง ์์๊ฐ?
๊ทธ๋ ๋ค. ๋ชจ๋ ํด๋์ค์ open ํค์๋๋ฅผ ๋ถ์ด๋ ์๊ณ ๋ฅผ ํ๋ค๋ฉด, ์ ์ด๋ ํ๋ก์ ๋ฌธ์ ์์๋ ์์ ํ๋ฌ๊ทธ์ธ์ด ๋ถํ์ํ ๊ฒ์ด๋ค.
ํ์ง๋ง Data Class๊ฐ ์๋ ์์ฑํ๋ equals()๋ ๋งค์ฐ ์๊ฒฉํ๋ฐ, this.javaClass == other.javaClass์ ๊ฐ์ด ํด๋์ค ํ์
๋ ๊ฒ์ฌํ๋ค. ์๋ณธ ์ํฐํฐ ํด๋์ค์ ํ๋ก์ ํด๋์ค๋ ์์ฐํ ๋ค๋ฅธ ํด๋์ค์ด๋ฏ๋ก, Data Class๋ฅผ ์ฐ๋ฉด ID๊ฐ ๊ฐ์๋ equals()๊ฐ false๋ฅผ ๋ฐํํ๋ ์น๋ช
์ ์ธ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ์ฌ ์์์ฑ ์ปจํ
์คํธ๊ฐ ์ ์ ์๋ํ์ง ์๋๋ค.
์ ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
data class๋ final์ด๋ผ ๋ณธ๋ ํ๋ก์๋ฅผ ์์ฑํ์ง ๋ชปํ์ง๋ง, kotlin-spring ํ๋ฌ๊ทธ์ธ์ด ๊ฐ์ ๋ก open์์ผ์ ํ๋ก์๋ฅผ ๋ง๋ ๋ค. ํ๋ก์๋ ๋ง๋ค์ด์ก์ง๋ง, data class์ equals()๋ ๊ฐ์ ํด๋์ค๋ง ์ธ์ ํ๊ธฐ ๋๋ฌธ์ ํ๋ก์๋ฅผ ๊ฑฐ๋ถํ๋ ๋ชจ์์ ์ธ ์ํฉ์ด ๋ฐ์ํ๋ค.
Use PK
๋ฐ๋ผ์ Entity๋ ์ ๋ data class๋ก ๋ง๋ค์ง ์๋๋ค.
๋ํ JPA Entity๋ฅผ ์ผ๋ฐ class๋ก ๋ง๋ค ๋๋, equals()์ hashCode()๋ฅผ ๊ฐ๋ณ์ ์ธ ํ๋๋ค๋ก ๋ง๋ค๋ฉด ์ ๋๋ค. ๋ณํ์ง ์๋ ์๋ณ์, ์ฆ Primary Key(PK)๋ก๋ง ๊ตฌ์ฑํ์ฌ Overridingํ์ฌ์ผ ํ๋ค.