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ํ•˜์—ฌ์•ผ ํ•œ๋‹ค.