Hibernate ORM 6.6 ๋ณ€๊ฒฝ ์‚ฌํ•ญ

Table of contents

1. Oracle ์•”์‹œ์  ๋ฐฐ์—ด ํƒ€์ž… ์ด๋ฆ„ ๊ฐœ์„ 

์ฃผ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์ƒํ™ฉ

  • Oracle DB์—์„œ Java ๋ฐฐ์—ด ํƒ€์ž… (Array<T>)์„ ์ปฌ๋Ÿผ ํƒ€์ž…์œผ๋กœ ์‚ฌ์šฉํ•  ๋•Œ

  • ์˜ˆ๋ฅผ ๋“ค์–ด Oracle์˜ ์‚ฌ์šฉ์ž ์ •์˜ ๋ฐฐ์—ด ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

  • ex) NUMBER[], VARCHAR2[], STRUCT[] ๊ฐ™์€ Oracle์˜ Collection ํƒ€์ž…

์˜ˆ์‹œ

CREATE OR REPLACE TYPE string_array AS TABLE OF VARCHAR2(100);
@Entity
@Table(name = "my_entity")
class MyEntity(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    @Column(name = "tags")
    var tags: Array<String>? = null
)

์ด ๋•Œ Hibernate๊ฐ€ Oracle์— tags ์ปฌ๋Ÿผ์„ ๋งคํ•‘ํ•˜๋ ค๊ณ  ํ•  ๋•Œ, ๋‚ด๋ถ€์ ์œผ๋กœ StringArray ๋ผ๋Š” ์ด๋ฆ„์˜ Oracle ๋ฐฐ์—ด ํƒ€์ž…์ด ํ•„์š”ํ•จ.

์ด๋•Œ ์ด๋ฆ„ ์ถฉ๋Œ๊ณผ ๋ช…ํ™•์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Hibernate 6.6 ๋ถ€ํ„ฐ StringArray โ†’ StringVarcharArray OR StringArrayUsingMyConverter ์ฒ˜๋Ÿผ ๋” ๋ช…ํ™•ํ•œ ์ด๋ฆ„์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•œ๋‹ค.

DB๋ฅผ Array ํƒ€์ž…์œผ๋กœ ์“ฐ๋Š” ๊ฒƒ VS Join Table VS Join ์ˆ˜๋™์˜ ์žฅ๋‹จ์ 

์ฃผ๋กœ MySQL์„ ์“ฐ๋‹ค๋ณด๋‹ˆ ์ปฌ๋Ÿผ ์ž์ฒด์— Array๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์—†์–ด์„œ ๊ถ๊ธˆํ•ด์„œ ๊ฐ ํ•ญ๋ชฉ์˜ ์žฅ๋‹จ์ ์— ๋Œ€ํ•ด์„œ ์ฐพ์•„๋ณด์•˜๋‹ค.

์ผ๋ฐ˜์ ์œผ๋กœ Join์„ ๋งŽ์ด ์“ฐ๋‚˜, ์šฐ๋ฆฌ ํšŒ์‚ฌ / ํŒ€์—์„œ๋Š” Join์„ ์ง์ ‘ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ˆ˜๋™์œผ๋กœ Join์„ ์‚ฌ์šฉํ•˜๋Š” ํŽธ์ด๋ผ ํ•ด๋‹น ๋ถ€๋ถ„๋„ ํ•จ๊ป˜ ๋น„๊ตํ•ด๋ณด์•˜๋‹ค.

  • PersonArray / Person + Phone์ด ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ ์˜ˆ์‹œ
ํ•ญ๋ชฉโ‘  ๋ฐฐ์—ด ์ปฌ๋Ÿผ ๋ฐฉ์‹ (Oracle ARRAY)โ‘ก ์ •๊ทœํ™” ๋ฐฉ์‹ (Join ์‚ฌ์šฉ) @OneToManyโ‘ข ์ •๊ทœํ™” ๋ฐฉ์‹ (Join ์—†์ด ์ˆ˜๋™ ์กฐํšŒ)
DB ๊ตฌ์กฐPersonArray ํ•˜๋‚˜์˜ ํ…Œ์ด๋ธ”์— ๋ฐฐ์—ด ์ปฌ๋Ÿผ ์‚ฌ์šฉ๋‘ ๊ฐœ์˜ ํ…Œ์ด๋ธ” + ์™ธ๋ž˜ ํ‚ค๋‘ ๊ฐœ์˜ ํ…Œ์ด๋ธ” + ์™ธ๋ž˜ ํ‚ค
์ฟผ๋ฆฌ ๋ณต์žก๋„๋‹จ์ผ ์ฟผ๋ฆฌ๋กœ ์กฐํšŒ ๊ฐ€๋ŠฅHibernate๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ JOIN ์ˆ˜ํ–‰๋‘ ๊ฐœ์˜ ๋ช…์‹œ์  ์ฟผ๋ฆฌ ํ•„์š” (person, phone)
์ฝ๊ธฐ ์„ฑ๋Šฅโœ” ๋น ๋ฆ„ (์ „์ฒด ๋ฐฐ์—ด ์ฝ๊ธฐ)โŒ JOIN ๋น„์šฉ ์žˆ์Œ (N+1 ๊ฐ€๋Šฅ์„ฑ ์žˆ์Œ)โœ” ํšจ์œจ์  (JOIN ์ตœ์†Œํ™”)
์“ฐ๊ธฐ ๋ณต์žก๋„โŒ ์ „์ฒด ๋ฐฐ์—ด ๊ฐฑ์‹  ํ•„์š”โœ” ๊ฐœ๋ณ„ ์—”ํ‹ฐํ‹ฐ ์‚ฝ์ž…/์‚ญ์ œ ๊ฐ€๋ŠฅโŒ ๋กœ์ง์„ ์ˆ˜๋™์œผ๋กœ ๊ตฌํ˜„ํ•ด์•ผ ํ•จ
๊ฒ€์ƒ‰ ์œ ์—ฐ์„ฑโŒ ๋ฐฐ์—ด ๋‚ด๋ถ€ ์กฐ๊ฑด ๊ฒ€์ƒ‰ ์–ด๋ ค์›€โœ” phone.number ๋กœ ํ•„ํ„ฐ๋ง ๊ฐ€๋Šฅโœ” ๋ช…์‹œ์  ์กฐ๊ฑด ์ฟผ๋ฆฌ ์ž‘์„ฑ ๊ฐ€๋Šฅ
ORM ๋งคํ•‘ ๋‚œ์ด๋„โœ” ๋งค์šฐ ๋‹จ์ˆœ (ํ•„๋“œ๋งŒ ์„ ์–ธ)โŒ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€ ์„ค์ • ๋“ฑ ๋ณต์žกโœ” ๋งคํ•‘์€ ๋‹จ์ˆœ, ์กฐํšŒ ๋กœ์ง์€ ์ˆ˜๋™
์—”ํ‹ฐํ‹ฐ ์ผ๊ด€์„ฑ ๋ณด์žฅโŒ ์•ฝํ•จ (์˜์†์„ฑ ์ „์ด ์—†์Œ)โœ” ์ „์ด, ๊ณ ์•„ ๊ฐ์ฒด ์ œ๊ฑฐ ๋“ฑ ๊ฐ€๋ŠฅโŒ ์ผ๊ด€์„ฑ ์ˆ˜๋™ ๋ณด์žฅ ํ•„์š”
์œ ์ง€๋ณด์ˆ˜์„ฑ์ค‘๊ฐ„ (๋‹จ์ˆœํ•˜์ง€๋งŒ ํ™•์žฅ์„ฑ ๋‚ฎ์Œ)๋†’์Œ (ํ‘œํ˜„๋ ฅ, ์œ ์—ฐ์„ฑ ์šฐ์ˆ˜)๋‚ฎ์Œ (์ง์ ‘ ์ฟผ๋ฆฌ ๊ด€๋ฆฌ ํ•„์š”)
๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ํ™•์žฅ ์šฉ์ด์„ฑโŒ ๋ฐฐ์—ด์€ ์ถ”๊ฐ€ ํ•„๋“œ ๋„ฃ๊ธฐ ์–ด๋ ค์›€โœ” ํ™•์žฅ ์šฉ์ด (์˜ˆ: ํƒ€์ž…, ๋“ฑ๋ก์ผ ๋“ฑ)โœ” ํ™•์žฅ ์šฉ์ด
์ถ”์ฒœ ์‚ฌ์šฉ ์‹œ์ ๋‹จ์ˆœ ์ €์žฅ/์กฐํšŒ, ๊ฐ’ ์ˆ˜ ์ ๊ณ  ์ „์ฒด๋งŒ ๋‹ค๋ฃฐ ๋•Œ๊ด€๊ณ„๊ฐ€ ๋ช…ํ™•ํ•˜๊ณ  ํ™•์žฅ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐJOIN ์ตœ์†Œํ™” + ์ˆ˜๋™ ํŠœ๋‹์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ

2. UserDefinedType ๊ตฌ์กฐ ๋ณ€๊ฒฝ

๊ธฐ์กด ๋ฌธ์ œ์ 

  • Oracle UDT(ex. STRUCT, VARRAY, NESTED TABLE)์„ ๋งคํ•‘ํ•  ๋•Œ ๊ตฌ์กฐ์  ์ฐจ์ด๋ฅผ ๋ช…ํ™•ํžˆ ๋ฐ˜์˜ํ•˜์ง€ ๋ชปํ•จ.

  • ex) STRUCT๋Š” ํ•„๋“œ ์ด๋ฆ„/ํƒ€์ž…์„ ๊ฐ€์ง€๊ณ  ์žˆ์ง€๋งŒ ARRAY๋Š” ๊ทธ๋ ‡์ง€ ์•Š์Œ

  • ํ•˜์ง€๋งŒ Hibernate ๋‚ด๋ถ€์—์„œ ๋ชจ๋‘ ๋™์ผํ•œ UserDefinedType ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ฒ˜๋ฆฌ๋จ

๊ฐœ์„  ๋ชฉ์ 

  • STRUCT, ARRAY๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„

  • ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž… ๊ฐ„ ์ข…์†์„ฑ๋„ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•จ.
    (ex. STRUCT์˜ ํ•„๋“œ๊ฐ€ ARRAY์ธ ๊ฒฝ์šฐ)

AS-IS (Hibernate 6.5 ์ดํ•˜)

interface UserDefinedType<T> {
    fun getUserTypeName(): String
    fun getJdbcType(): JdbcType
    ...
}

TO-BE (Hibernate 6.6)

interface UserDefinedType<T> {
    fun getUserTypeName(): String
    ...
}

interface UserDefinedObjectType<T> : UserDefinedType<T> {
    fun getAttributes(): List<...> // STRUCT ์ „์šฉ
}

interface UserDefinedArrayType<T> : UserDefinedType<T> {
    fun getElementType(): JdbcType
}

์ฆ‰, ์ƒ์œ„ ํƒ€์ž…(UserDefinedType)์€ ๊ณตํ†ต ์ธํ„ฐํŽ˜์ด์Šค, ํ•˜์œ„์— ObjectType (STRUCT), ArrayType(VARRAY ๋“ฑ)์ด ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌ ๋จ.

์–ด๋–ค ์ƒํ™ฉ์— ์‚ฌ์šฉํ• ๊นŒ?

  • ๊ณ ๊ธ‰ Oracle UDT ๋งคํ•‘์‹œ ์‚ฌ์šฉ ๋จ
-- 1. Oracle STRUCT ํƒ€์ž… ์˜ˆ์‹œ
CREATE TYPE address_type AS OBJECT (
  city VARCHAR2(100),
  zipcode VARCHAR2(10)
);
-- 2. Oracle ARRAY of STRUCT
CREATE TYPE address_array AS TABLE OF address_type;
  • Kotlin ๋งคํ•‘ ์˜ˆ์‹œ
// Address ๊ตฌ์กฐ์ฒด
data class Address(
    val city: String,
    val zipcode: String
)

// ์ฃผ์†Œ ๋ชฉ๋ก (Oracle ARRAY of STRUCT)
@Entity
class Customer(
    @Id @GeneratedValue
    val id: Long? = null,

    @JdbcTypeCode(SqlTypes.ARRAY)
    @Column(name = "addresses")
    var addresses: Array<Address>? = null
)

Hibernate๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ UserDefinedArrayType<Address> โ†’ UserDefinedObjectType<Address> ๊ตฌ์กฐ๋กœ ์ธ์‹ํ•œ๋‹ค.

UserDefinedObjectType<T>

  • Oracle์˜ STRUCT ํƒ€์ž…์„ ์˜๋ฏธ

  • ex) address_type(city VARCHAR2, zipcode VARCHAR2)

  • ๋‹จ์ผ ๊ตฌ์กฐ์ฒด ํ•˜๋‚˜. data class Address(โ€ฆ) ์™€ ๋Œ€์‘๋œ๋‹ค.

UserDefinedArrayType<T>

  • Oracle์˜ TABLE OF STRUCT, ์ฆ‰ STRUCT์˜ ๋ฐฐ์—ด์„ ์˜๋ฏธํ•จ.

  • ex) adress_array AS TABLE OF address_type

  • ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๊ตฌ์กฐ์ฒด. Array<Address> ์™€ ๋Œ€์‘ ๋œ๋‹ค.

Hibernate๊ฐ€ ์ดํ•ดํ•˜๋Š” ๋‚ด๋ถ€ ๋ชจ๋ธ

UserDefinedArrayType<Address> {
    elementType = UserDefinedObjectType<Address> {
        attributes = ["city" โ†’ String, "zipcode" โ†’ String]
    }
}
  • ์ด ๋ฐฐ์—ด์€ Address๋ผ๋Š” ๊ตฌ์กฐ์ฒด์˜ ๋ฐฐ์—ด์ด๊ณ , ๊ทธ Address๋ผ๋Š” ๊ตฌ์กฐ์ฒด๋Š” ๋‘ ๊ฐœ์˜ ํ•„๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ  ๊ทธ๊ฑด ๊ฐ๊ฐ VARCHAR2 ๋ผ๋Š” ๊ฒƒ์„ ์ธ์‹ํ•˜๊ณ  ๋งคํ•‘ ์ •๋ณด๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.

์ด ๊ตฌ์กฐ์˜ ์žฅ์ 

  • ์ž๋™ SQL ์ƒ์„ฑ โ†’ Hibernate๊ฐ€ ์ ์ ˆํ•œ Oracle SQL ํƒ€์ž…๊ณผ DDL์„ ์ •ํ™•ํžˆ ์ƒ์„ฑํ•จ

  • ์ค‘์ฒฉ ํƒ€์ž… ์ง€์› โ†’ STRUCT ์•ˆ์— ๋˜ ๋‹ค๋ฅธ ARRAY๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ๋„ ๋ชจ๋ธ๋ง ๊ฐ€๋Šฅ

  • ๋ช…ํ™•ํ•œ ํƒ€์ž… ์˜์กด์„ฑ ์ถ”์  โ†’ ์–ด๋–ค ๋ฐฐ์—ด์ด ์–ด๋–ค ๊ตฌ์กฐ์ฒด๋ฅผ ์“ฐ๋Š”์ง€ Hibernate๊ฐ€ ๊ตฌ์กฐ์ ์œผ๋กœ ์ถ”์ 

  • ๋‹คํ˜•์„ฑ ๋ฐ ํƒ€์ž… ์•ˆ์ •์„ฑ ๊ฐ•ํ™” โ†’ ๋ฉ”ํƒ€์ •๋ณด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ๋Ÿฐํƒ€์ž„์— ํƒ€์ž… ์ถฉ๋Œ ๋ฐฉ์ง€ ๊ฐ€๋Šฅ

๋งคํ•‘ ์˜ˆ์‹œ

data class Address(val city: String, val zipcode: String)

@Entity
class Customer(
    ...
    @JdbcTypeCode(SqlTypes.ARRAY)
    var addresses: Array<Address>? = null
)
  • addresses ํ•„๋“œ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ, UserDefinedArrayType<Address>๋กœ ์ธ์‹๋˜๊ณ , ๊ทธ ์•ˆ์— ๋“ค์–ด๊ฐ€๋Š” Address๋Š” UserDefinedObjectType<Address>๋กœ ์ธ์‹๋œ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด Oracle UDT์— ๋Œ€ํ•œ DDL ์ž๋™ ์ƒ์„ฑ์ด ๊ฐ€๋Šฅํ• ๊นŒ?

Hibernate 6.6์—์„œ๋„ Oracle์˜ UDT(STRUCT, VARRAY, NESTED TABLE)์— ๋Œ€ํ•œ DDL์€ ์—ฌ์ „ํžˆ ์ž๋™ ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š”๋‹ค. (ex hibernate.hbm2ddl.auto=create ์„ค์ • ๋“ฑ)

  • ํ•˜์ง€๋งŒ Hibernate 6.6์—์„œ๋Š” ๋‚ด๋ถ€ ๊ตฌ์กฐ ์ธ์‹ ๋ฐ ํƒ€์ž… ์ด๋ฆ„ ์ƒ์„ฑ์ด ๋” ์ •๊ตํ•ด์ ธ์„œ ๋งคํ•‘๊ณผ ๋ฐ”์ธ๋”ฉ์€ ๋” ์ž˜ ๋™์ž‘ํ•œ๋‹ค.

Hibernate๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ VS ์•ˆํ•˜๋Š” ๊ฒƒ

ํ•ญ๋ชฉ์ž๋™ ์ƒ์„ฑ ์—ฌ๋ถ€์„ค๋ช…
Oracle ํ…Œ์ด๋ธ” (CREATE TABLE)โœ… ๊ฐ€๋Šฅ์—”ํ‹ฐํ‹ฐ์— ๊ธฐ๋ฐ˜ํ•œ ํ…Œ์ด๋ธ” ๋ฐ ์ปฌ๋Ÿผ ์ƒ์„ฑ ๊ฐ€๋Šฅ
Oracle STRUCT ํƒ€์ž… (CREATE TYPE ... AS OBJECT)โŒ ๋ถˆ๊ฐ€๋ŠฅOracle ์ „์šฉ ๋ฌธ๋ฒ•, Hibernate๊ฐ€ ์ƒ์„ฑํ•˜์ง€ ์•Š์Œ
Oracle ๋ฐฐ์—ด ํƒ€์ž… (CREATE TYPE ... AS TABLE OF)โŒ ๋ถˆ๊ฐ€๋Šฅ๋ฐฐ์—ด ํƒ€์ž…๋„ ์ˆ˜๋™ ์ƒ์„ฑ ํ•„์š”
Hibernate ๋งคํ•‘ ๋ฉ”ํƒ€๋ชจ๋ธโœ… ๊ฐ€๋ŠฅSTRUCT/ARRAY ํƒ€์ž…์˜ ๋งคํ•‘ ๋ฉ”ํƒ€์ •๋ณด ์ƒ์„ฑ
Oracle ๋ฐฐ์—ด ํƒ€์ž… ์ด๋ฆ„ ์ถ”๋ก โœ… ๊ฐ€๋ŠฅHibernate 6.6๋ถ€ํ„ฐ ๋” ์ •๋ฐ€ํ•˜๊ณ  ์ถฉ๋Œ ๋ฐฉ์ง€๋จ
Hibernate insert/update/delete SQLโœ… ๊ฐ€๋Šฅ์ƒ์„ฑ๋œ UDT ํƒ€์ž…์„ ์ฐธ์กฐํ•˜์—ฌ SQL ์‹คํ–‰ ๊ฐ€๋Šฅ
Hibernate schema validatorโœ… ๊ฐ€๋Šฅ์ˆ˜๋™ ์ƒ์„ฑ๋œ UDT์™€ ๋งคํ•‘์ด ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆ ๊ฐ€๋Šฅ (validate)

Hibernate๊ฐ€ CREATE TYPE์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ด์œ 

  1. JPA ์‚ฌ์–‘์— ์—†์Œ

    • @Entity, @Embeddable ๋“ฑ์€ ํ…Œ์ด๋ธ”๊ณผ ์ปฌ๋Ÿผ์„ ์ •์˜ํ•˜์ง€, Oracle์˜ TYPE์„ ์ •์˜ํ•˜๋Š” ์‚ฌ์–‘์ด ์กด์žฌํ•˜์ง€ ์•Š์Œ
  2. DBMS์— ์ข…์†์ ์ธ ๋ฌธ๋ฒ•

    • CREATE TYPE์€ ๊ฐ DB๋งˆ๋‹ค ๋ฌธ๋ฒ•์ด ์ „ํ˜€ ๋‹ค๋ฆ„
  3. Hibernate์˜ ์Šคํ‚ค๋งˆ ์ƒ์„ฑ๊ธฐ๋Š” ANSI SQL ๊ธฐ๋ฐ˜

    • Oracle ์ „์šฉ UDT๋Š” HIbernate๊ฐ€ ์ธ์‹์€ ํ•˜์ง€๋งŒ ์ง์ ‘ ์ƒ์„ฑํ•˜์ง€๋Š” ์•Š์Œ

Hibernate์™€ Oracle UDT๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ• ๊นŒ?

  1. Oracle UDT ์ˆ˜๋™ ์ƒ์„ฑ
CREATE OR REPLACE TYPE address_type AS OBJECT (
  city VARCHAR2(100),
  zipcode VARCHAR2(10)
);

CREATE OR REPLACE TYPE address_array AS TABLE OF address_type;
  1. Hibernate๋Š” ํ•ด๋‹น ํƒ€์ž…์˜ ์ด๋ฆ„๋งŒ ์ฐธ์กฐ
@Column(columnDefinition = "address_array")
@JdbcTypeCode(SqlTypes.ARRAY)
var addresses: Array<Address>? = null

Hibernate 6.6์˜ ์—ญํ• 

  • STRUCT/ARRAY๋ฅผ ๊ตฌ๋ถ„ํ•˜์—ฌ ํƒ€์ž… ๋ฉ”ํƒ€์ •๋ณด ์ถ”์ 

  • UDT์˜ ์˜์กด์„ฑ ๊ด€๊ณ„๊นŒ์ง€ ์ถ”์  ๊ฐ€๋Šฅ (์˜ˆ. STRUCT ์•ˆ์— ARRAY๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ)

  • ์ž๋™ ํƒ€์ž… ์ด๋ฆ„ ์ƒ์„ฑ์ด ๋” ๋ช…ํ™•ํ•˜๊ณ  ์ถฉ๋Œ ์—†๋Š” ๋ฐฉ์‹์‘๋กœ ๊ฐœ์„ ๋จ

3. ๋ฐฐ์—ด ๋ถ€๋ถ„ ํฌํ•จ ์—ฌ๋ถ€ ๊ฒ€์‚ฌ ํ•จ์ˆ˜ ๋ณ€๊ฒฝ

Hibernate 6.5 ์ดํ•˜ ๋ฒ„์ „์˜ ๋ฌธ์ œ์ 

where array_contains(tags, ARRAY['urgent', 'vip'])
  • Hibernate 6.5 ์ดํ•˜์—์„œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ JPQL ๋ฌธ์„ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•œ๋ฐ, ์ด ํ•จ์ˆ˜๋Š” ์›๋ž˜ โ€œ๋ฐฐ์—ด์— ๋‹จ์ผ ๊ฐ’์ด ํฌํ•จ๋˜๋Š”๊ฐ€?โ€๋ฅผ ์˜๋„ํ•œ ๊ฒƒ

  • ๋ฐฐ์—ด ๋Œ€ ๋ฐฐ์—ด ๋น„๊ต์— ์‚ฌ์šฉ๋˜๋Š” ๊ฒƒ์€ ๋น„ํ‘œ์ค€ ์‚ฌ์šฉ

Hibernate 6.6์—์„œ ๋ณ€๊ฒฝ๋œ ์ 

ํ•จ์ˆ˜ ๋ฐฉ์‹

where array_includes(tags, ARRAY['urgent', 'vip'])
  • tags ๋ฐฐ์—ด์ด [โ€˜urgentโ€™, โ€˜vipโ€™]๋ฅผ ํฌํ•จํ•˜๋Š”์ง€ ํ™•์ธ

  • ๋‚ด๋ถ€์ ์œผ๋กœ PostgreSQL/Oracle์˜ @>, ANY, ALL ๋“ฑ์— ๋งคํ•‘ ๋จ

์ˆ ์–ด ๋ฐฉ์‹

where tags INCLUDES ARRAY['urgent', 'vip']

์ฃผ์˜์‚ฌํ•ญ

  • ๋ฐฐ์—ด ํƒ€์ž… ์ปฌ๋Ÿผ์ด์–ด์•ผ ์ž‘๋™ํ•จ (@JdbcTypeCode(SqlTypes.ARRAY))

  • DB๊ฐ€ ๋ฐฐ์—ด ์—ฐ์‚ฐ์„ ์ง€์›ํ•ด์•ผ ํ•œ๋‹ค. (MySQL์€ ๋ฐฐ์—ด ํƒ€์ž… ์ง€์› X)

  • INCLUDES๋Š” Hibernate๊ฐ€ ๋งŒ๋“  JPQL ์ˆ ์–ด๋กœ JPA ํ‘œ์ค€์—๋Š” ์—†์Œ

4. ์‚ญ์ œ๋œ Entity ๋ณ‘ํ•ฉ ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๊ฐ•ํ™”

  • JPA์˜ EntityManager.merge() ์‚ฌ์šฉ ์‹œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋น„์ •์ƒ์ ์ธ insert ๋™์ž‘์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋„์ž…๋œ ์•ˆ์ •์„ฑ ๊ฐœ์„  ๊ธฐ๋Šฅ

  • Hibernate 6.6๋ถ€ํ„ฐ๋Š” merge() ํ˜ธ์ถœ ์‹œ DB์— ํ•ด๋‹น ํ–‰์ด ์กด์žฌํ•˜์ง€ ์•Š์œผ๋ฉด, ์กฐ๊ฑด์ด ์ถฉ์กฑ๋  ๊ฒฝ์šฐ OptimisticLockException ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค. (์˜๋„์น˜ ์•Š์€ INSERT ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ํ•จ)

๊ธฐ์กด ๋ฌธ์ œ์  (Hibernate 6.5 ์ดํ•˜)

๋ฌธ์ œ ์ƒํ™ฉ

  1. ์–ด๋–ค ์—”ํ‹ฐํ‹ฐ๊ฐ€ A ํŠธ๋žœ์žญ์…˜์—์„œ ์‚ญ์ œ๋จ

  2. B ํŠธ๋žœ์žญ์…˜์—์„œ๋Š” ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ์˜ ์ฐธ์กฐ๋ฅผ ์—ฌ์ „ํžˆ ๋“ค๊ณ  ์žˆ์Œ (detached ์ƒํƒœ)

  3. B ํŠธ๋žœ์žญ์…˜์—์„œ em.merge(entity) ํ˜ธ์ถœ

๊ธฐ์กด ๋™์ž‘ (๋ฌธ์ œ O)

  • Hibernate๋Š” ํ•ด๋‹น ID์˜ ์—”ํ‹ฐํ‹ฐ๊ฐ€ DB์— ์—†์œผ๋ฏ€๋กœ ์ƒˆ๋กœ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ํŒ๋‹จํ•ด INSERT ์‹คํ–‰

  • ์‹ค์ œ๋กœ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•œ๊ฑด ๋ณ‘ํ•ฉ(Update) ์˜€์ง€๋งŒ, ์ด๋ฏธ ์‚ญ์ œ๋œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋‹ค์‹œ ์ƒ์„ฑ๋จ.

  • ๋‚™๊ด€์  ๋ฝ(Optimistic Locking)์˜ ๊ทœ์น™ ์œ„๋ฐ˜์ด๋ฉฐ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ํ•ด์น˜๋Š” ์ƒํ™ฉ

Hibernate 6.6์˜ ๊ฐœ์„ ๋œ ๋™์ž‘

DB์— ํ•ด๋‹น row๊ฐ€ ์—†์„ ๊ฒฝ์šฐ OptimisticLockException์„ ๋˜์ง

์˜ˆ์™ธ ๋ฐœ์ƒ ์กฐ๊ฑด

  1. @Id ๊ฐ€ @GeneratedValue ๋“ฑ์œผ๋กœ ์ž๋™ ์ƒ์„ฑ๋œ ๊ฐ’์ผ ๊ฒฝ์šฐ

  2. @Version ํ•„๋“œ๊ฐ€ nullable ๋ž˜ํผ ํƒ€์ž…์ผ ๊ฒฝ์šฐ (ex. Long?, Integer?)

์ด ๋‘๊ฐ€์ง€ ์ค‘ ํ•˜๋‚˜์˜ ์กฐ๊ฑด์ด๋ผ๋„ ๋งŒ์กฑํ•˜๋ฉด Hibernate๋Š” ์ด ๊ฐ์ฒด๊ฐ€ ์›๋ž˜ ์กด์žฌํ•œ ๊ฐ์ฒด๋ผ๊ณ  ํŒ๋‹จํ•  ์ˆ˜ ์žˆ์Œ. ๋”ฐ๋ผ์„œ DB์— ์—†์œผ๋ฉด ์‚ญ์ œ๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผํ•จ.

์˜ˆ์‹œ

@Entity
class Product(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    var name: String,

    @Version
    var version: Long? = null
)
--
interface ProductRepository : JpaRepository<Product, Long>
--
@Service
class ProductService(
    private val productRepository: ProductRepository
) {

    @Transactional
    fun simulateMergeAfterDelete() {
        // 1. ์ €์žฅ
        val saved = productRepository.save(Product(name = "Item"))

        // 2. ์‚ญ์ œ
        productRepository.deleteById(saved.id!!)

        // 3. ์‚ญ์ œ๋œ ๊ฐ์ฒด๋ฅผ ๋“ค๊ณ  ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ณ , ์ˆ˜์ •ํ•ด์„œ ๋‹ค์‹œ ์ €์žฅ ์‹œ๋„
        val detached = Product(id = saved.id, name = "Updated Item", version = saved.version)

        // 4. ๋‹ค์‹œ ์ €์žฅ (์ด ์‹œ์ ์— Hibernate ๋‚ด๋ถ€์ ์œผ๋กœ merge ๋ฐœ์ƒ)
        productRepository.save(detached)  // Hibernate 6.6: OptimisticLockException ๋ฐœ์ƒ ๊ฐ€๋Šฅ
    }
}

๋‚™๊ด€์  ๋ฝ(Optimistic Lock)์„ ์œ„๋ฐ˜ํ•˜๋Š” ์ด์œ 

๋‚™๊ด€์  ๋ฝ์ด๋ž€?

  • ๋™์‹œ์„ฑ ์ œ์–ด ๊ธฐ๋ฒ• ์ค‘ ํ•˜๋‚˜

  • โ€œ์ถฉ๋Œ์ด ๊ฑฐ์˜ ์—†์„ ๊ฒƒโ€์ด๋ผ๊ณ  ๋‚™๊ด€์ ์œผ๋กœ ๊ฐ€์ •ํ•˜๊ณ , ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์‹œ์ ์—๋งŒ ์ถฉ๋Œ์„ ๊ฐ์ง€ํ•จ.

  • @Version ํ•„๋“œ๋ฅผ ํ†ตํ•ด ๋ฒ„์ „์„ ๋น„๊ตํ•˜์—ฌ ์ถฉ๋Œ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•จ.

๋™์‹œ์„ฑ ์ถฉ๋Œ์˜ ์˜ˆ์‹œ

  • A ํŠธ๋žœ์žญ์…˜์—์„œ Product๋ฅผ ์กฐํšŒํ•œ ํ›„ ์‚ญ์ œํ•จ

  • B ํŠธ๋žœ์žญ์…˜์€ ์กฐํšŒ๋œ ์—”ํ‹ฐํ‹ฐ๋ฅผ save()(=merge) ํ•˜๋ ค ํ•จ

  • ์ด ๋•Œ DB์— ์ด๋ฏธ ์—†๋Š” ์ƒํƒœ์ผ ๋•Œ Hibernate์˜ ๋™์ž‘์€?

Hibernate 6.6์˜ ๊ฒฝ์šฐ

  • ๊ฐ์ฒด๋ฅผ ์ฝ์„ ๋•Œ version = 1์ด๋ผ๋ฉด, ์—…๋ฐ์ดํŠธ ์‹œ์—๋„ ์—ฌ์ „์ด version = 1์ด์–ด์•ผ ํ•จ

  • ๋งŒ์•ฝ ํ•ด๋‹น row๊ฐ€ ์—†๊ฑฐ๋‚˜ version์ด ๋‹ค๋ฅด๋ฉด โ†’ OptimisticLockException์ด ๋ฐœ์ƒํ•จ

select version from product where id = ?  -- id = 1
  • ๊ฒฐ๊ณผ ์—†์Œ โ†’ ์‚ญ์ œ๋œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ

  • ๊ทธ๋ฆฌ๊ณ  @Version์ด nullable(Long?)์ด๊ธฐ์— โ€œ์›๋ž˜ ์กด์žฌํ•˜๋˜ ๊ฐ์ฒดโ€๋กœ ํŒ๋‹จ ๊ฐ€๋Šฅ

  • ์ดํ›„ ์‚ญ์ œ๋œ ์—”ํ‹ฐํ‹ฐ์— ๋Œ€ํ•œ ๋ณ‘ํ•ฉ ์‹œ๋„ โ†’ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•จ.

์ด์ „ Hibernate๋Š”?

  • merge ๋Œ€์ƒ์ด DB์— ์—†์œผ๋ฉด ๊ทธ๋ƒฅ INSERT ์ง„ํ–‰

  • ์‚ญ์ œ๋œ ์ค„๋„ ๋ชจ๋ฅด๊ณ  ์ƒˆ๋กœ ๋งŒ๋“ค์–ด ๋ฒ„๋ฆฌ๋Š” ๋™์ž‘์„ ์ทจํ•จ

  • ๋™์‹œ์„ฑ ์ถฉ๋Œ์„ ๊ฐ์ง€ํ•˜์ง€ ์•Š๊ณ  ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒˆ๋กœ ์ƒ๊ธฐ๋ฏ€๋กœ ๋‚™๊ด€์  ๋ฝ์˜ ์ฒ ํ•™์„ ์–ด๊ธฐ๊ฒŒ ๋จ.

์˜ˆ์‹œ) ์‹ค๋ฌด์—์„œ ๊ฐœ์„ ๋˜๋Š” ๊ฒƒ

  • ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์—์„œ ์˜ค๋ž˜๋œ ์—”ํ‹ฐํ‹ฐ ์ด๋ฒคํŠธ๊ฐ€ ๋ณ‘ํ•ฉ๋  ์œ„ํ—˜

  • ๊ด€๋ฆฌ์ž ํ™”๋ฉด ๋“ฑ์—์„œ UI์— ๋…ธ์ถœ๋œ ์‚ญ์ œ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์‹ค์ˆ˜๋กœ ์ˆ˜์ •๋  ๊ฐ€๋Šฅ์„ฑ

  • ๋ฉ€ํ‹ฐ ์œ ์ € ํ™˜๊ฒฝ์—์„œ ๋™์‹œ ์‚ญ์ œ/์ˆ˜์ •์ด ์ถฉ๋Œ๋‚  ๊ฒฝ์šฐ

๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ ๋ณดํ˜ธ์— ํฐ ํšจ๊ณผ๊ฐ€ ์žˆ์Œ

5. ํด๋ž˜์Šค ์–ด๋…ธํ…Œ์ด์…˜ ์ค‘๋ณต ์‚ฌ์šฉ ๊ธˆ์ง€

ํ•œ ํด๋ž˜์Šค์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ JPA ๋ฉ”ํƒ€ ์–ด๋…ธํ…Œ์ด์…˜์„ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ํ—ˆ์šฉ๋˜์ง€ ์•Š์Œ.

Hibernate 6.5 ์ดํ•˜

  • @MappedSuperclass + @Entity

  • @Embeddable + @Entity

  • @Embeddable + @MappedSuperclass

์œ„์™€ ๊ฐ™์€ ์–ด๋…ธํ…Œ์ด์…˜ ์กฐํ•ฉ์ด ๊ฐ€๋Šฅํ–ˆ๋‹ค.

๊ฐ ์–ด๋…ธํ…Œ์ด์…˜์˜ ์—ญํ• 

  • @Entity โ†’ ๋…๋ฆฝ์ ์ธ ํ…Œ์ด๋ธ”๋กœ ๋งคํ•‘๋˜๋Š” ์‹ค์ œ ์—”ํ‹ฐํ‹ฐ ํด๋ž˜์Šค

  • @MappedSuperclass โ†’ ํ…Œ์ด๋ธ”์€ ์—†์ง€๋งŒ ์ƒ์† ์‹œ ํ•„๋“œ๊ฐ€ ํ•˜์œ„ ์—”ํ‹ฐํ‹ฐ์— ํฌํ•จ๋จ.

  • @Embeddable โ†’ ๋ณ„๋„ ํ…Œ์ด๋ธ” ์—†์ด, ๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ์— ํฌํ•จ๋˜๋Š” ๊ฐ’ ๊ฐ์ฒด ํƒ€์ž…

์„ž์–ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ๋‚ด๋ถ€์ ์œผ๋กœ ๋งค์šฐ ๋ชจํ˜ธํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง

๋ชจํ˜ธํ•จ์„ ์ œ๊ฑฐํ•˜๊ธฐ ์œ„ํ•ด Hibernate 6.6์—์„œ๋Š” ๋™์‹œ ์‚ฌ์šฉ ์ž์ฒด๋ฅผ ๊ธˆ์ง€ํ•จ.

Hibernate 6.6์˜ ๋‚ด๋ถ€ ๋ณ€ํ™”

  • ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ์‹œ ์—”ํ‹ฐํ‹ฐ ์Šค์บ” ๊ณผ์ •์—์„œ ํด๋ž˜์Šค์˜ ์—ญํ• ์„ ๋ช…ํ™•ํžˆ ์‹๋ณ„ํ•ด์•ผ ํ•˜๋ฉฐ, org.hibernate.metamodel.model.domain.internal.ClassTypeDeterminer ๊ณ„์ธต์—์„œ ์ค‘๋ณต ์ •์˜๋œ ์–ด๋…ธํ…Œ์ด์…˜์„ ๊ฒ€์‚ฌํ•œ๋‹ค.

  • ์ฆ‰, ์• ๋งคํ•œ ํด๋ž˜์Šค๊ฐ€ ํ”„๋กœ์ ํŠธ์— ์žˆ์œผ๋ฉด ๋ถ€ํŒ… ์ž์ฒด๊ฐ€ ์‹คํŒจํ•œ๋‹ค.

@MappedSuperclass VS @Emabeddable ๋น„๊ต

ํ•ญ๋ชฉ@MappedSuperclass@Embeddable
์—ญํ• ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒ์†๋ฐ›๋Š” ์ถ”์ƒ ๋ถ€๋ชจ ํด๋ž˜์Šค๋‹ค๋ฅธ ์—”ํ‹ฐํ‹ฐ ๋‚ด๋ถ€์— ํฌํ•จ๋˜๋Š” ๊ฐ’ ๊ฐ์ฒด
ํ…Œ์ด๋ธ” ์กด์žฌ ์—ฌ๋ถ€โŒ ๋ณ„๋„์˜ ํ…Œ์ด๋ธ” ์—†์ŒโŒ ๋ณ„๋„์˜ ํ…Œ์ด๋ธ” ์—†์Œ
์ƒ์† ์—ฌ๋ถ€๋ฐ˜๋“œ์‹œ extends ๋˜๋Š” : ์œผ๋กœ ์ƒ์†๋ฐ›์•„์•ผ ํ•จํฌํ•จ(aggregation) ๊ด€๊ณ„, @Embedded๋กœ ์ฃผ์ž…
๋ฐ์ดํ„ฐ ํฌํ•จ ๋ฐฉ์‹์ž์‹ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋ถ€๋ชจ ํ•„๋“œ๋“ค์„ ์ง์ ‘ ํฌํ•จํ•จํฌํ•จ๋œ ํ•„๋“œ ์•ˆ์— ๊ฐ’์„ ํ•˜๋‚˜์˜ ๊ฐ์ฒด์ฒ˜๋Ÿผ ํฌํ•จ
์ƒ์†๋œ ํ•„๋“œ ์‚ฌ์šฉ์ž์‹ ์—”ํ‹ฐํ‹ฐ์˜ ์ปฌ๋Ÿผ์œผ๋กœ ์ง์ ‘ ํฌํ•จ๋จ๊ฐ์ฒด ํ•„๋“œ ์•ˆ์— ์†์„ฑ์œผ๋กœ ํฌํ•จ๋จ (audit.createdAt)
์—”ํ‹ฐํ‹ฐ๋กœ ์ธ์‹๋จ?โŒ ์ง์ ‘ @Entity ์•„๋‹˜โŒ ์ง์ ‘ @Entity ์•„๋‹˜
์‚ฌ์šฉ ์˜ˆ์‹œ๊ณตํ†ต ํ•„๋“œ๋ฅผ ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ์— ์ƒ์†ํ•˜๊ณ  ์‹ถ์„ ๋•Œํ•„๋“œ ๊ทธ๋ฃน์„ ๊ตฌ์กฐํ™”ํ•˜๊ณ  ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์„ ๋•Œ
์ฟผ๋ฆฌ์—์„œ์˜ ํ‘œํ˜„SELECT o.createdAt FROM Order oSELECT o.audit.createdAt FROM Order o

์‹ค๋ฌด์—์„œ ์„ ํƒ ๊ธฐ์ค€

  • ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ์— ๊ณตํ†ต ํ•„๋“œ๋ฅผ ์ƒ์†ํ•ด์•ผ ํ•จ โ†’ @MappedSuperclass

  • ๊ณตํ†ต ํ•„๋“œ๊ฐ€ ํ•˜๋‚˜์˜ ๊ฐœ๋… ๋‹จ์œ„(๊ฐ์ฒด)๋กœ ๋ฌถ์—ฌ์•ผ ํ•จ โ†’ @Embeddable

  • ๋‹จ์ผ ๊ฐ์ฒด ์•ˆ์—์„œ ์„œ๋ธŒ ์†์„ฑ์œผ๋กœ ํ‘œํ˜„ํ•˜๊ณ  ์‹ถ์Œ โ†’ @Embeddable

  • JPA์˜ ๋‹คํ˜•์„ฑ ์ƒ์† ์ „๋žต๊ณผ ํ•จ๊ป˜ ์“ฐ๊ณ  ์‹ถ์Œ โ†’ @MappedSuperclass ๋˜๋Š” @Entity

6. @Embeddable ๋‹คํ˜•์„ฑ ์ง€์› (Discriminator ๊ธฐ๋ฐ˜)

Hibernate 6.6๋ถ€ํ„ฐ๋Š” ๊ฐ’ ํƒ€์ž…(@Embeddable)์—์„œ๋„ ๋‹คํ˜•์„ฑ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ

๊ฐ’ ํƒ€์ž…(Embeddable)์ด๋ž€?

  • ์‹๋ณ„์ž๊ฐ€ ์—†๊ณ  ์ƒ๋ช… ์ฃผ๊ธฐ๋Š” ์†Œ์œ ํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ์— ์ข…์†๋˜๋ฉฐ, ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ์—์„œ ๊ณต์œ  ๊ฐ€๋Šฅํ•˜๊ณ , ๋…๋ฆฝ ํ…Œ์ด๋ธ”์ด ์—†๋Š”(์ปฌ๋Ÿผ์œผ๋กœ ๋งคํ•‘๋จ) ํƒ€์ž…

๊ฐ’ ํƒ€์ž…์„ ์“ฐ๋Š” ์ƒํ™ฉ

  • ์ฃผ์†Œ, ๊ธฐ๊ฐ„, ์ขŒํ‘œ, ๊ธˆ์•ก, ํ†ตํ™” ๋“ฑ ์ž‘์€ ์˜๋ฏธ ๋‹จ์œ„๋กœ ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ

  • ๊ณตํ†ต ํ•„๋“œ ๊ตฌ์กฐํ™”๋ฅผ ์œ„ํ•ด (createdAt, updatedAt, createdBy ๋“ฑ)

  • ํ•˜๋‚˜์˜ ๋…ผ๋ฆฌ์  ๋‹จ์œ„๋กœ ๋ฌถ์–ด์•ผ ํ•  ๋•Œ (ex. Money(currency, amount))

์žฅ/๋‹จ์ 

์žฅ์ 
  • ์žฌ์‚ฌ์šฉ์„ฑ โ†’ ์—ฌ๋Ÿฌ ์—”ํ‹ฐํ‹ฐ์—์„œ ๊ณตํ†ต ๊ตฌ์กฐ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

  • ์ฝ”๋“œ ๊ฐ„๊ฒฐ์„ฑ โ†’ ์ค‘๋ณต ์ค„์ด๊ณ  ์œ ์ง€๋ณด์ˆ˜ ์šฉ์ด

  • ์˜๋ฏธ ์žˆ๋Š” ๋ชจ๋ธ๋ง โ†’ ํ•„๋“œ๋ฅผ ๊ฐ์ฒด ๋‹จ์œ„๋กœ ํ‘œํ˜„

  • ๋ณ€๊ฒฝ ๊ฐ์ง€ โ†’ ์†Œ์œ  ์—”ํ‹ฐํ‹ฐ์—์„œ ๊ฐ’ ํƒ€์ž… ํ•„๋“œ ๋ณ€๊ฒฝ ๊ฐ์ง€ ๊ฐ€๋Šฅ (dirty checking)

๋‹จ์ 
  • ์‹๋ณ„์ž๊ฐ€ ์—†์Œ โ†’ ๊ฐœ๋ณ„๋กœ ์กฐํšŒ/์ถ”์  ๋ถˆ๊ฐ€๋Šฅ

  • ๊ด€๊ณ„ ์„ค์ • ๋ถˆ๊ฐ€ โ†’ @ManyToOne, @OneToMany ๋“ฑ ๊ด€๊ณ„ ์„ค์ • ๋ถˆ๊ฐ€๋Šฅ

  • ๋ณ€๊ฒฝ ๋ถ„๋ฆฌ ์–ด๋ ค์›€ โ†’ ๊ฐ’ ํƒ€์ž… ์ž์ฒด๋กœ ๋ณ€๊ฒฝํ•˜๋ ค๋ฉด ์ „์ฒด ๊ฐ์ฒด๋ฅผ ๊ต์ฒดํ•ด์•ผ ํ•จ.

  • ์ƒ์†/๋‹คํ˜•์„ฑ ํ•œ๊ณ„ โ†’ Hibernate ์ด์ „๊นŒ์ง€๋Š” ๋‹คํ˜•์„ฑ ๋ถˆ๊ฐ€ (์ด์ œ ์ง€์›๋จ)

๊ฐ’ ํƒ€์ž…์ด ๋ณ€๊ฒฝ ๋ถ„๋ฆฌ๊ฐ€ ์–ด๋ ค์šด ์ด์œ 

๊ฐ’ ํƒ€์ž…์˜ ํŠน์ง•์—์„œ ๋ณผ ์ˆ˜ ์žˆ์Œ
  • ๋ถˆ๋ณ€(immutable)ํ•˜๊ฒŒ ์„ค๊ณ„ํ•˜๋Š”๊ฒŒ ์ด์ƒ์ ์ž„

  • ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ๋กœ ํฌํ•จ๋˜๋ฉฐ, ์ฐธ์กฐ๊ฐ€ ์•„๋‹Œ ๊ฐ’ ์ž์ฒด๋กœ ๋น„๊ต๋˜๊ณ  ๊ด€๋ฆฌ๋œ๋‹ค.

  • JPA๋Š” ๊ฐ’ ํƒ€์ž…์„ โ€œ๊ทธ ๊ฐ’ ์ž์ฒด๊ฐ€ ๋ฐ”๋€Œ์—ˆ๋Š”์ง€โ€๋ฅผ ํ™•์ธํ•œ๋‹ค.

๊ฐ’ ํƒ€์ž…์˜ ๊ฐ’ ์ผ๋ถ€๋งŒ ์ˆ˜์ •ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ
  • ๊ฐ’ ํƒ€์ž… ํ•„๋“œ ์ค‘ ํ•˜๋‚˜๋งŒ ๋ฐ”๊พธ๋ ค๊ณ  ํ•ด๋„, JPA๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ์ „์ฒด ๊ฐ’์ด ๋‹ฌ๋ผ์กŒ๋Š”์ง€ ๊ธฐ์ค€์œผ๋กœ ๋ณ€๊ฒฝ ์—ฌ๋ถ€๋ฅผ ํŒ๋‹จํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ถ€๋ถ„ ์ˆ˜์ •์ด ์•„๋‹ˆ๋ผ ์ „์ฒด ๊ฐ์ฒด ๊ต์ฒด๊ฐ€ ๋˜์–ด์•ผ ํ•œ๋‹ค.

์˜ˆ์‹œ

@Embeddable
data class Address(
    val city: String,
    val street: String
)
@Entity
class Customer(
    @Id @GeneratedValue
    val id: Long? = null,

    var name: String,

    @Embedded
    var address: Address
)

์œ„์™€ ๊ฐ™์€ ์ƒํ™ฉ์ผ ๋•Œ,

  • ์ž˜๋ชป๋œ ๋ฐฉ์‹ โ†’ ํ•„๋“œ ์ง์ ‘ ์ˆ˜์ •
val customer = customerRepository.findById(id).get()

// address๋Š” data class๋ผ ๋‚ด๋ถ€ ํ•„๋“œ ์ง์ ‘ ์ˆ˜์ • ๋ถˆ๊ฐ€
customer.address.city = "NewCity"  // ์ปดํŒŒ์ผ ์—๋Ÿฌ
  • ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ์‹ โ†’ ์ „์ฒด ๊ต์ฒด
val customer = customerRepository.findById(id).get()

// ๊ธฐ์กด address๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒˆ๋กœ์šด ๊ฐ์ฒด ์ƒ์„ฑ
val updatedAddress = customer.address.copy(city = "NewCity")

// ์ƒˆ Address ๊ฐ์ฒด๋กœ ๊ต์ฒด
customer.address = updatedAddress

JPA๋Š” @Embedded ํ•„๋“œ์˜ ์ฐธ์กฐ๊ฐ€ ๋ฐ”๋€Œ์—ˆ์Œ์„ ๊ฐ์ง€ํ•˜๊ณ  ์ „์ฒด ๊ฐ’ ๋ณ€๊ฒฝ์œผ๋กœ ๊ฐ„์ฃผํ•˜๊ณ  UPDATE๋ฅผ ์ˆ˜ํ–‰ํ•œ๋‹ค.

  • ํ•„๋“œ๋ฅผ ์ง์ ‘ ์ˆ˜์ •ํ•˜๋ฉด ๋”ํ‹ฐ ์ฒดํ‚น์ด ์•ˆ๋˜๊ฑฐ๋‚˜ ๋ฌด์‹œ๋  ์ˆ˜ ์žˆ๋‹ค.

    • @DynamicUpdate ๋“ฑ์„ ์จ๋„ ์™„๋ฒฝํžˆ ๋ณด์žฅ๋˜์ง€ ์•Š์Œ
  • ๋ถˆ๋ณ€ ๊ฐ์ฒด(data class + copy)๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์„ค๊ณ„์ƒ ๋” ์•ˆ์ •ํ•˜๊ณ  JPA ์ฒ ํ•™์—๋„ ๋งž๋‹ค.

Hibernate 6.5 ์ดํ•˜์—์„œ์˜ ๊ฐ’ ํƒ€์ž…

  • @Embeddable์€ ๋‹จ์ผ ๊ตฌ์กฐ๋กœ๋งŒ ์ทจ๊ธ‰์ด ๋จ (์ƒ์†ํ•ด๋„ ํ•˜์œ„ ํด๋ž˜์Šค ์ •๋ณด ์œ ์ง€ ์•ˆ๋จ)

  • type() ๋˜๋Š” treat() JPQL์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Œ

Hibernate 6.6 ๋ถ€ํ„ฐ

  • @Embeddable ํƒ€์ž…๋„ ์ƒ์† ๊ตฌ์กฐ๋ฅผ ์ง€์›

  • Hibernate๊ฐ€ ์ž๋™์œผ๋กœ Discriminator ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ ํ•จ

  • JPQL์—์„œ type(), treat()์œผ๋กœ ํ•˜์œ„ ํƒ€์ž… ํ•„ํ„ฐ๋ง ๊ฐ€๋Šฅ

์˜ˆ์ œ

๋ถ€๋ชจ ํด๋ž˜์Šค (๊ฐ’ ํƒ€์ž… ์ƒ์† ๊ตฌ์กฐ)

@Embeddable
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)  // Hibernate 6.6์—์„œ ์ž๋™ ์ฒ˜๋ฆฌ๋จ
abstract class Address(
    open val city: String
)

ํ•˜์œ„ ํด๋ž˜์Šค (๋‹คํ˜•์„ฑ ๊ตฌํ˜„)

@Embeddable
class KoreanAddress(
    override val city: String,
    val postalCode: String
) : Address(city)
--
@Embeddable
class ForeignAddress(
    override val city: String,
    val countryCode: String
) : Address(city)

์—”ํ‹ฐํ‹ฐ์—์„œ ์‚ฌ์šฉ

@Entity
class Customer(
    @Id @GeneratedValue
    val id: Long? = null,

    val name: String,

    @Embedded
    var address: Address
)

โ†’ Hibernate๋Š” address_type์ด๋ผ๋Š” Discriminator ์ปฌ๋Ÿผ์„ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๊ณ , KoreanAddress, ForeignAddress ์ค‘ ์–ด๋–ค ํ•˜์œ„ ํƒ€์ž…์ธ์ง€ ๊ธฐ๋กํ•จ.

์ƒ์„ฑ๋˜๋Š” ํ…Œ์ด๋ธ” ์˜ˆ์‹œ

idnameaddress_cityaddress_postal_codeaddress_country_codeaddress_type
1KimSeoul12345NULLKoreanAddress
2JohnLondonNULLGBForeignAddress

์‹ค๋ฌด ์ ์šฉ์‹œ

  • ๊ณตํ†ต ๊ตฌ์กฐ ์ •์˜ โ†’ ๊ณตํ†ต ํ•„๋“œ๋Š” ๋ถ€๋ชจ @Embeddable ํด๋ž˜์Šค์— ์ •์˜

  • ํ•˜์œ„ ๊ตฌ์กฐ ์ •์˜ โ†’ ๊ณ ์œ  ํ•„๋“œ๋Š” ์ž์‹ ํด๋ž˜์Šค์— ์ •์˜

  • ์กฐํšŒ์‹œ ์œ ์—ฐ์„ฑ โ†’ JPQL์—์„œ ํ•˜์œ„ ํƒ€์ž… ๊ตฌ๋ถ„ ํ•„ํ„ฐ๋ง ๊ฐ€๋Šฅ (type())

  • ์ฟผ๋ฆฌ ํ‘œํ˜„๋ ฅ โ†’ treat() ์‚ฌ์šฉ ๊ฐ€๋Šฅ โ†’ ๊ฐ’ ํƒ€์ž…์—์„œ๋„ ๋‹คํ˜•์„ฑ ์กฐ์ž‘ ๊ฐ€๋Šฅ

  • ์ฟผ๋ฆฌ ์ตœ์ ํ™” โ†’ ๊ฐ’ ํƒ€์ž…์ด์ง€๋งŒ ์กฐ๊ฑด๋ณ„ ํ•„ํ„ฐ๋ง์ด ๊ฐ€๋Šฅํ•ด์ ธ ์„ฑ๋Šฅ ํ–ฅ์ƒ ๊ฐ€๋Šฅ

์ฟผ๋ฆฌ ์ตœ์ ํ™”

๊ธฐ์กด (Hibernate 6.5 ์ดํ•˜)

  • @Embeddable ํƒ€์ž…์€ ์ผ๋ฐ˜ ๊ฐ์ฒด์ฒ˜๋Ÿผ๋งŒ ๋™์ž‘ํ•˜๊ณ , ํƒ€์ž… ๊ตฌ๋ถ„์ด ์•ˆ ๋จ.

  • ์˜ˆ๋ฅผ ๋“ค์–ด KoreanAddress, ForeignAddress๋ฅผ ๋ชจ๋‘ Address๋กœ ์ €์žฅํ•˜๋ฉด

    • ์ฟผ๋ฆฌ์—์„œ๋Š” ๋‘˜์„ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†์Œ

    • ๋ถˆํ•„์š”ํ•œ ์ „์ฒด ์Šค์บ”์ด ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹จ์—์„œ ํ•„ํ„ฐ๋ง์„ ํ•ด์•ผ ํ•จ.

Hibernate 6.6 ์ดํ›„

  • Discriminator ๊ธฐ๋ฐ˜์œผ๋กœ ์ฟผ๋ฆฌ ๋‹จ์—์„œ ํ•˜์œ„ ํƒ€์ž…์„ ์ •ํ™•ํžˆ ํ•„ํ„ฐ๋ง ๊ฐ€๋Šฅ

  • Hibernate๊ฐ€ @Embeddable ํƒ€์ž…์— ๋Œ€ํ•ด ๋‹คํ˜•์„ฑ์„ ์ง€์›ํ•˜๊ณ  Discriminator ์ปฌ๋Ÿผ์„ ์ƒ์„ฑํ•จ

    • ๋”ฐ๋ผ์„œ DB ๋ ˆ๋ฒจ์—์„œ ์ •ํ™•ํ•œ ์กฐ๊ฑด ํ•„ํ„ฐ๋ง ๊ฐ€๋Šฅ

      • address_type ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค๋ฅผ ๊ฑธ๊ฑฐ๋‚˜ ์ •ํ™•ํ•œ where ์กฐ๊ฑด์„ ์ค„ ์ˆ˜ ์žˆ์Œ
    • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‹จ์—์„œ ํƒ€์ž… ํ•„ํ„ฐ๋ง ๋ถˆํ•„์š”

    • ๋ถˆํ•„์š”ํ•œ ์กฐ์ธ ์—†์ด ๊ฐ’ ํƒ€์ž… ์กฐ๊ฑด๋งŒ์œผ๋กœ ํ•„ํ„ฐ๋ง

7. H2 Bulk Mutation ์ „๋žต ๋ณ€๊ฒฝ

๋Œ€๋Ÿ‰ ์‚ญ์ œ(DELETE), ๋Œ€๋Ÿ‰ ์ˆ˜์ •(UPDATE)๋ฅผ ์‹คํ–‰ํ•  ๋•Œ, H2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๋ฌด๊ฒฐ์„ฑ ์ œ์•ฝ ์œ„๋ฐ˜ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ ค๋Š” ๋ชฉ์ ์œผ๋กœ ๋ณ€๊ฒฝ๋จ.

๋ณ€๊ฒฝ ์ „ vs ํ›„

  • ๋ณ€๊ฒฝ ์ „ โ†’ Hibernate๋Š” H2์—์„œ inline ๋ฐฉ์‹์œผ๋กœ bulk update/delete๋ฅผ ์‹คํ–‰ ํ•จ

  • ๋ณ€๊ฒฝ ํ›„ โ†’ H2์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ temporary table ๋ฐฉ์‹์œผ๋กœ ์‹คํ–‰๋˜๋„๋ก ๋ณ€๊ฒฝ ๋จ.

  • ๋ชฉ์  โ†’ FK ์ œ์•ฝ ์กฐ๊ฑด ์œ„๋ฐ˜ ์˜ค๋ฅ˜ ๋ฐฉ์ง€ ๋ฐ ์‹ค์ œ RDBMS์™€ ์œ ์‚ฌํ•œ ๋™์ž‘์„ ๋ณด์žฅํ•จ.

Bulk Mutation ์ „๋žต์ด๋ž€?

Hibernate์—์„œ Bulk Mutation์€ JPQL ๋˜๋Š” HQL์„ ์ด์šฉํ•œ ๋Œ€๋Ÿ‰ ์ˆ˜์ •/์‚ญ์ œ ๊ตฌ๋ฌธ์„ ์‹คํ–‰ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋‚ด๋ถ€ ์ „๋žต

  • INLINE โ†’ SQL์„ ์ง์ ‘ DELETE, UPDATE๋กœ ์‹คํ–‰ํ•จ

  • TEMP TABLE โ†’ ๋จผ์ € ์‚ญ์ œํ• /์—…๋ฐ์ดํŠธํ•  ID๋“ค์„ ์ž„์‹œ ํ…Œ์ด๋ธ”์— ์ €์žฅ ํ›„ ์ฒ˜๋ฆฌํ•จ.

  • CTE (WITH ๋ฌธ) โ†’ ๊ณตํ†ต ํ…Œ์ด๋ธ” ํ‘œํ˜„์‹(Common Table Expression)์„ ์‚ฌ์šฉํ•จ.

H2 DB์—์„œ์˜ ๊ธฐ์กด ๋ฌธ์ œ (INLINE ์ „๋žต)

  • Hibernate์˜ INLINE ๋ฐฉ์‹์€ ์ˆœ์„œ ์—†์ด ์‚ญ์ œํ•จ โ†’ FK ์ถฉ๋Œ ๋ฐœ์ƒ

H2์˜ FK ์ œ์•ฝ ์กฐ๊ฑด

H2๋Š” ๋‹ค๋ฅธ RDBMS๋ณด๋‹ค ์™ธ๋ž˜ ํ‚ค ์ œ์•ฝ์„ ๋” ์—„๊ฒฉํ•˜๊ฒŒ ์ฆ‰์‹œ ํ‰๊ฐ€(eager evaluation)ํ•จ.

  • ๋Œ€๋ถ€๋ถ„์˜ ์‹ค์ œ RDBMS(ex. MySQL, Oracle ๋“ฑ)๋Š”

    • FK ์ œ์•ฝ์„ ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹ ์‹œ์ ๊นŒ์ง€ defer(์ง€์—ฐ ํ‰๊ฐ€)ํ•  ์ˆ˜๋„ ์žˆ๊ณ 

    • ๋Œ€๋Ÿ‰ ์‚ญ์ œ ์‹œ ๋‚ด๋ถ€์ ์œผ๋กœ ์ˆœ์„œ๋ฅผ ์กฐ์ •ํ•˜๋ ค ์ถฉ๋Œ ์—†์ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Œ

  • H2์˜ ๊ฒฝ์šฐ

    • ์‚ญ์ œ/์ˆ˜์ • SQL ์‹คํ–‰ ์‹œ์ ์— ๋ฐ”๋กœ FK ๋ฌด๊ฒฐ์„ฑ ์กฐ๊ฑด์„ ๊ฒ€์‚ฌํ•จ

    • ํŠธ๋žœ์žญ์…˜ ๋‚ด์—์„œ๋ผ๋„ FK ์ถฉ๋Œ์ด ์žˆ์œผ๋ฉด ๋ฐ”๋กœ ์˜ˆ์™ธ๋ฅผ ๋˜์ง (ํ…Œ์ŠคํŠธ ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋  ์ˆ˜ ์žˆ์Œ)

H2์—์„œ ์ž์ฃผ ๋ฌธ์ œ๋˜๋Š” ์ƒํ™ฉ

  • JPQL๋กœ ๋Œ€๋Ÿ‰ ์‚ญ์ œ ์‹œ: Hibernate๊ฐ€ DELETE FROM Parent๋ฅผ ๋จผ์ € ์‹คํ–‰ โ†’ FK ์˜ค๋ฅ˜ ์ฆ‰์‹œ ๋ฐœ์ƒ

  • @Transactional ์•ˆ์—์„œ save โ†’ delete ์‹œ๋„: ์ €์žฅ์ด ๋˜๊ธฐ ์ „์— delete๊ฐ€ ํŠธ๋ฆฌ๊ฑฐ ๋˜๋ฉด FK ๊ฒ€์‚ฌ ์‹คํŒจ

  • bulk ์—ฐ์‚ฐ ์ˆœ์„œ๊ฐ€ ๋ถ€์ ์ ˆํ•œ ๊ฒฝ์šฐ: ์ž์‹ โ†’ ๋ถ€๋ชจ ์ˆœ์ด ์•„๋‹ˆ๋ฉด ์˜ค๋ฅ˜

H2๋Š” ์‹ค์ œ RDBMS๋ณด๋‹ค ๋” ์ฆ‰์‹œ, ํŠธ๋žœ์žญ์…˜๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ์™ธ๋ž˜ํ‚ค ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํ…Œ์ŠคํŠธ ์ค‘ bulk ์‚ญ์ œ๋‚˜ ์ˆ˜์ •์ด ์˜ˆ์ƒ๋ณด๋‹ค ๋” ์ž์ฃผ ์‹คํŒจํ•  ์ˆ˜ ์žˆ์Œ.

Hibernate 6.6 ๋ณ€๊ฒฝ ๋‚ด์šฉ (TEMP TABLE ์ „๋žต)

  • ์‚ญ์ œ/์ˆ˜์ • ๋Œ€์ƒ ID๋ฅผ ์ž„์‹œ ํ…Œ์ด๋ธ” (hibernate_temp_ids)์— ์ €์žฅํ•จ

  • ์ดํ›„ ์ด ์ž„์‹œ ํ…Œ์ด๋ธ”์„ ์ด์šฉํ•ด DELETE, UPDATE ์ˆ˜ํ–‰

  • FK๊ฐ€ ๊ฑธ๋ ค ์žˆ์–ด๋„ ์•ˆ์ „ํ•˜๊ฒŒ ์ˆœ์„œ๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Œ

์„ค์ • ๋ฐฉ๋ฒ•

hibernate.hql.bulk_id_strategy=org.hibernate.hql.spi.id.inline.InlineIdsInClauseBulkIdStrategy
hibernate.hql.bulk_id_strategy=org.hibernate.hql.spi.id.table.LocalTemporaryTableBulkIdStrategy  // ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋ณ€๊ฒฝ๋จ
  • Hibernate 6.6์—์„œ H2 ๊ธฐ๋ณธ ์ „๋žต์„ TEMP TABLE๋กœ ๋ฐ”๊ฟจ์ง€๋งŒ, ์›ํ•œ๋‹ค๋ฉด ์ „๋žต์„ ๋ช…์‹œ์ ์œผ๋กœ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.

8. Expression#as(Class) โ†’ case() ๋ช…ํ™•ํ™”

Criteria API ์—์„œ ํƒ€์ž… ๋ณ€ํ™˜ ๋˜๋Š” ์กฐ๊ฑด ๋ถ„๊ธฐ๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ ํ˜ผ๋™์„ ์ค„์ด๊ณ , ๋ช…ํ™•ํ•˜๊ณ  ์ผ๊ด€๋œ API ์‚ฌ์šฉ์„ ์œ ๋„ํ•˜๊ธฐ ์œ„ํ•ด ๋ณ€๊ฒฝํ•จ.

  • ๊ฐœ๋ฐœ์ž๋“ค์ด as(class)๋ฅผ ํƒ€์ž… ๋ณ€ํ™˜ / ์กฐ๊ฑด ๋ถ„๊ธฐ / ๋‹คํ˜•์„ฑ ์ฒ˜๋ฆฌ๋กœ ํ˜ผ๋™ํ•ด์„œ ์‚ฌ์šฉํ•จ.

Hibernate 6.6์˜ ๊ฐœ์„  ์‚ฌํ•ญ

  • ํƒ€์ž… ๋ณ€ํ™˜ โ†’ expression.as(Class) (๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ)

  • ์กฐ๊ฑด ๋ถ„๊ธฐ (CASE WHEN) โ†’ criteriaBuilder.select(criteriaBuilder.<Type>selectCase()โ€ฆ)

  • ๋‹คํ˜•์„ฑ ์บ์ŠคํŒ… โ†’ treat() ์‚ฌ์šฉ

Criteria API์—์„œ ํ˜ผ๋™ ๋˜๋Š” ์˜ˆ์‹œ

-- JPQL
SELECT CASE WHEN o.amount > 100 THEN 'HIGH' ELSE 'LOW' END FROM Order o
criteriaBuilder.select(orderRoot.get("amount").as(String.class))  
// as() ์˜๋ฏธ ๋ถˆ๋ถ„๋ช…
// ์ž๋™์œผ๋กœ HIGH, LOW๋กœ ์บ์ŠคํŒ… ๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋‚˜, ์˜คํ•ดํ•ด์„œ ๋งŽ์ด ์‚ฌ์šฉ ํ•จ.
  • Hibernate 6.6์—์„œ๋Š” ๋‹ค์Œ ์ฒ˜๋Ÿผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋„๋ก ์œ ๋„ํ•จ.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<String> cq = cb.createQuery(String.class);
Root<Order> root = cq.from(Order.class);

cq.select(
    cb.selectCase()
      .when(cb.gt(root.get("amount"), 100), "HIGH")
      .otherwise("LOW")
);

Criteria ๋ž€?

JPA์—์„œ ๋งํ•˜๋Š” Criteria API๋Š” SQL์ฒ˜๋Ÿผ ๋ฌธ์ž์—ด๋กœ ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ , Java ๋˜๋Š” Kotlin ์ฝ”๋“œ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๋™์ ์œผ๋กœ ์กฐ๋ฆฝํ•  ์ˆ˜ ์žˆ๋„๋ก ๋งŒ๋“  ํ‘œ์ค€ API

์–ธ์ œ, ์™œ ์“ธ๊นŒ?

  • ๋™์  ์ฟผ๋ฆฌ ์กฐ๋ฆฝ โ†’ ์กฐ๊ฑด์ด ์„ ํƒ์ ์œผ๋กœ ๋ฐ”๋€” ๊ฒฝ์šฐ (if, for, switch๋กœ ์กฐ๊ฑด ์ถ”๊ฐ€ ๊ฐ€๋Šฅ)

  • ํƒ€์ž… ์•ˆ์ •์„ฑ โ†’ ์ปดํŒŒ์ผ ํƒ€์ž„์— ์ปฌ๋Ÿผ ์ด๋ฆ„ ์˜คํƒ€๋‚˜ ํƒ€์ž… ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Œ

  • IDE ์ž๋™ ์™„์„ฑ โ†’ Kotlin/Java ๊ตฌ์กฐ ๊ธฐ๋ฐ˜์œผ๋กœ IDE์—์„œ ์ฝ”๋“œ ์ž๋™์™„์„ฑ ๊ฐ€๋Šฅ

  • ์žฅ์  โ†’ JPA ํ‘œ์ค€ (๋ชจ๋“  ๊ตฌํ˜„์ฒด์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ)

  • ๋‹จ์  โ†’ ์ฝ”๋“œ๊ฐ€ ์žฅํ™ฉํ•˜๊ณ  ๋ณต์žกํ•ด์ง โ†’ ์‹ค๋ฌด์—์„œ๋Š” ๋‹ค๋ฅธ ์ฟผ๋ฆฌ ๊ธฐ์ˆ ์„ ๋งŽ์ด ์„ ํ˜ธ

Criteria API ์™€ ๋‹ค๋ฅธ ์ฟผ๋ฆฌ ๊ธฐ์ˆ ๋“ค๊ณผ์˜ ๋น„๊ต

ํ•ญ๋ชฉCriteria APIQueryDSLSpring Data SpecificationKotlin DSL (Spring 3.0+)
๊ธฐ๋ณธ ์„ฑ๊ฒฉJPA ํ‘œ์ค€ API์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌSpring Data ํ™•์žฅSpring ๊ณต์‹ Kotlin DSL
๊ฐ€๋…์„ฑโŒ ๋‚ฎ์Œโœ” ๋†’์Œโ—‘ ์ค‘๊ฐ„โœ” ๋†’์Œ
IDE ์ž๋™์™„์„ฑโ—‘ ๋ณดํ†ตโœ” ๋งค์šฐ ์ข‹์ŒโŒ ์—†์Œ (String ๊ธฐ๋ฐ˜)โœ” Kotlin ์นœํ™”์ 
์„ค์ • ๋‚œ์ด๋„์—†์Œโ›” ๋ณต์žก (APT, build ํ•„์š”)๊ฐ„๋‹จ๊ฐ„๋‹จ
Kotlin ํ˜ธํ™˜์„ฑโ—‘ ์ œํ•œ์ โŒ Java ์ „์šฉ์ด ๋งŽ์Œโœ” Kotlin ๊ฐ€๋Šฅโœ” ์ตœ์ ํ™”๋จ
์กฐ๊ฑด ๋™์  ์กฐ๋ฆฝโœ” ๋›ฐ์–ด๋‚จโœ” ๋›ฐ์–ด๋‚จโœ” ๋›ฐ์–ด๋‚จโœ” ๋›ฐ์–ด๋‚จ
์ปค๋ฎค๋‹ˆํ‹ฐ/๋ฌธ์„œ๐Ÿ“˜ ๋งŽ์Œ (JPA ํ‘œ์ค€)๐Ÿ“š ๋งŽ์Œ๐Ÿ“„ ๊ฐ„๊ฒฐ๐Ÿ“˜ Spring 3.0 ์ด์ƒ ๊ณต์‹ ์ง€์›

์–ด๋–ค ๊ธฐ์ค€์œผ๋กœ ์„ ํƒํ•ด์•ผํ• ๊นŒ?

๊ฐœ๋ฐœ ํ™˜๊ฒฝ์ถ”์ฒœ ๋ฐฉ์‹
์Šคํ”„๋ง + KotlinSpring Data Kotlin DSL, Specification
์Šคํ”„๋ง + JavaQueryDSL ๋˜๋Š” Criteria
๋™์  ์ฟผ๋ฆฌ ์กฐ๋ฆฝ์ด ๋งŽ์€ ๊ฒฝ์šฐCriteria API ๋˜๋Š” QueryDSL
๋‹จ์ˆœ ์กฐ๊ฑด + ๋น ๋ฅธ ๊ฐœ๋ฐœSpring Data JPA ๋ฉ”์„œ๋“œ ์ฟผ๋ฆฌ or Specification
๋ณต์žกํ•œ ๊ฒ€์ƒ‰ API (๊ด€๋ฆฌ์ž, ํ•„ํ„ฐ ๋“ฑ)QueryDSL or Criteria API

9. @Table + SINGLE_TABLE ์ƒ์† ์ „๋žต ์ถฉ๋Œ ๊ฒ€์ถœ

JPA์—์„œ ์ƒ์† ์ „๋žต์„ ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋Š” ์‹ค์ˆ˜๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•œ ์ œ์•ฝ ์ถ”๊ฐ€

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)

  • JPA์˜ ์ƒ์† ๋งคํ•‘ ์ „๋žต ์ค‘ ํ•˜๋‚˜

  • ๋ถ€๋ชจ ํด๋ž˜์Šค์™€ ์ž์‹ ํด๋ž˜์Šค์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•จ.

  • Hibernate๋Š” ์ด๋ฅผ ์œ„ํ•ด Discriminator ์ปฌ๋Ÿผ์„ ์ž๋™ ์ƒ์„ฑํ•ด์„œ ์–ด๋–ค ์ž์‹ ํƒ€์ž…์ธ์ง€ ๊ตฌ๋ถ„ํ•จ

@Table(name = โ€ฆ )

  • ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋งคํ•‘๋  ์‹ค์ œ ํ…Œ์ด๋ธ” ์ด๋ฆ„์„ ์ง€์ •ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ํด๋ž˜์Šค ์ด๋ฆ„์„ ๋”ฐ๋ฅด์ง€๋งŒ, ์›ํ•˜๋Š” ํ…Œ์ด๋ธ”๋ช…์„ ์ง์ ‘ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์Œ

๋ฌธ์ œ ์ƒํ™ฉ (Hibernate 6.5 ์ดํ•˜)

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@Table(name = "animal")  // ๋ถ€๋ชจ ํ…Œ์ด๋ธ” ์ง€์ •
open class Animal(
    @Id @GeneratedValue
    val id: Long? = null
)

@Entity
@Table(name = "cat")  // SINGLE_TABLE ์ „๋žต์ธ๋ฐ ์ž์‹์—๊ฒŒ ๋‹ค๋ฅธ ํ…Œ์ด๋ธ” ์ง€์ •
class Cat(
    val sound: String
) : Animal()
  • SINGLE_TABLE ์ „๋žต์€ ๋ถ€๋ชจ + ์ž์‹ ๋ชจ๋‘ ํ•œ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•ด์•ผ ํ•จ.

  • ๊ทธ๋Ÿฐ๋ฐ ์ž์‹ ํด๋ž˜์Šค Cat์— @Table(name = โ€œcatโ€)์„ ์ง€์ •ํ•˜๋ฉด ์ด ์ž์‹ ํด๋ž˜์Šค๋Š” ๋ณ„๋„ ํ…Œ์ด๋ธ”์„ ์“ฐ๊ฒ ๋‹ค๋Š” ๋œป

  • Hibernate์˜ ๋งคํ•‘ ์ „๋žต๊ณผ ์ถฉ๋Œ ๋ฐœ์ƒ

  • Hibernate 6.5 ์ดํ•˜๋Š” ์ด๊ฑธ ๊ฐ์ง€ํ•˜์ง€ ์•Š๊ณ  ๋ฌต์ธํ•˜๊ฑฐ๋‚˜ ๋Ÿฐํƒ€์ž„์— ๋ชจํ˜ธํ•œ ์˜ค๋ฅ˜๋กœ ์ด์–ด์ง.

Hibernate 6.6์—์„œ์˜ ๋ณ€๊ฒฝ์ 

๋ถ€๋ชจ๊ฐ€ SINGLE_TABLEl ์ „๋žต์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ

  • ์ž์‹ ํด๋ž˜์Šค์— @Table ๋ช…์‹œํ•˜๋ฉด ์ฆ‰์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ

  • MappingException: A @Table annotation on a subclass of a SINGLE_TABLE inheritance hierarchy is not allowed

์‹ค๋ฌด์—์„œ ์ด๋Ÿฐ ์ฝ”๋“œ๋ฅผ ์“ฐ๋Š” ์ด์œ 

  • ์ž์‹ ์—”ํ‹ฐํ‹ฐ๋งˆ๋‹ค ํ…Œ์ด๋ธ”์„ ๋”ฐ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์–ด์„œ

    • ์ „๋žต ์ž์ฒด๋ฅผ JOINED๋‚˜ TABLE_PER_CLASS๋กœ ๋ฐ”๊ฟจ์–ด์•ผ ํ•จ
  • SINGLE_TABLE ์ „๋žต์„ ์“ฐ๋ฉด์„œ๋„ ํ…Œ์ด๋ธ” ๋ช…์„ ๋ช…ํ™•ํžˆ ํ•˜๊ธฐ ์œ„ํ•ด ์ž˜๋ชป ๋ถ™์ธ ๊ฒฝ์šฐ

JPA์˜ 3๊ฐ€์ง€ ์ƒ์† ์ „๋žต

SINGLE_TABLE (๋‹จ์ผ ํ…Œ์ด๋ธ” ์ „๋žต)

  • ๋ถ€๋ชจ ํด๋ž˜์Šค์™€ ์ž์‹ ํด๋ž˜์Šค์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์˜ ํ…Œ์ด๋ธ”์— ์ €์žฅํ•œ๋‹ค.

  • ๊ตฌ๋ถ„์„ ์œ„ํ•ด Hibernate๊ฐ€ ์ž๋™์œผ๋กœ Discriminator ์ปฌ๋Ÿผ์„ ์ƒ์„ฑํ•ด ์–ด๋–ค ์ž์‹ ํƒ€์ž…์ธ์ง€ ๊ตฌ๋ถ„ํ•œ๋‹ค.

์žฅ์ 
  • ์„ฑ๋Šฅ ์ข‹์Œ โ†’ ์กฐ์ธ ์—†์ด ํ•œ ํ…Œ์ด๋ธ”๋งŒ ์กฐํšŒ

  • ๊ตฌํ˜„ ๋‹จ์ˆœ โ†’ ํ…Œ์ด๋ธ”์ด ํ•˜๋‚˜๋ฟ์ด๋ผ ์ฟผ๋ฆฌ๊ฐ€ ๋น ๋ฅด๊ณ  ๊ตฌ์กฐ ๋‹จ์ˆœ

  • ๊ธฐ๋ณธ ์ „๋žต โ†’ JPA์˜ ๊ธฐ๋ณธ ์ƒ์† ์ „๋žต์ด๋ผ ์„ค์ • ์ƒ๋žต ์‹œ ์‚ฌ์šฉ๋จ

๋‹จ์ 
  • NULL ์ปฌ๋Ÿผ ๋งŽ์•„์ง โ†’ ์ž์‹๋งˆ๋‹ค ํ•„๋“œ๊ฐ€ ๋‹ค๋ฅด๋ฉด ๋Œ€๋ถ€๋ถ„ ์ปฌ๋Ÿผ์ด NULL์ด ๋จ

  • ์Šคํ‚ค๋งˆ ์ •๊ทœํ™” ๊นจ์ง โ†’ ํ…Œ์ด๋ธ”์ด ์ปค์ง€๊ณ  ์ค‘๋ณต๋œ ์˜๋ฏธ ์—†๋Š” ์ปฌ๋Ÿผ ์ฆ๊ฐ€

  • @Table ์ถฉ๋Œ ์ฃผ์˜ โ†’ Hibernate 6.6๋ถ€ํ„ฐ ์ž์‹์— @Table ์‚ฌ์šฉ ์‹œ ์˜ˆ์™ธ ๋ฐœ์ƒ

์˜ˆ์‹œ
-- ๋‹จ์ผ ํ…Œ์ด๋ธ”์— ๋ชจ๋“  ํด๋ž˜์Šค ์ •๋ณด ์ €์žฅ
id | dtype     | name   | sound   | breed
---|-----------|--------|---------|-------
1  | Cat       | nabi   | meow    | NULL
2  | Dog       | choco  | NULL    | shiba
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
open class Animal(@Id @GeneratedValue val id: Long? = null, val name: String)

@Entity
@DiscriminatorValue("Cat")
class Cat(val sound: String) : Animal()

@Entity
@DiscriminatorValue("Dog")
class Dog(val breed: String) : Animal()

JOINED (์กฐ์ธ ์ „๋žต)

  • ๋ถ€๋ชจ ํด๋ž˜์Šค๋Š” ๊ณตํ†ต ํ•„๋“œ๋งŒ ํ…Œ์ด๋ธ”๋กœ ์ƒ์„ฑ

  • ์ž์‹ ํด๋ž˜์Šค๋Š” ์ž์‹ ๊ณ ์œ  ํ•„๋“œ๋งŒ ๋ณ„๋„ ํ…Œ์ด๋ธ”๋กœ ์ƒ์„ฑ

  • ์กฐํšŒ ์‹œ ๋ถ€๋ชจ-์ž์‹ ํ…Œ์ด๋ธ” ๊ฐ„ JOIN ์ˆ˜ํ–‰

์žฅ์ 
  • ์ •๊ทœํ™” ๊ตฌ์กฐ โ†’ ํ…Œ์ด๋ธ”์ด ๋…ผ๋ฆฌ์ ์œผ๋กœ ๊น”๋”ํ•˜๊ณ  ์ค‘๋ณต ์—†์Œ

  • ์ž์‹ ํ…Œ์ด๋ธ”์— ๊ณ ์œ  ์ œ์•ฝ ๊ฐ€๋Šฅ โ†’ ์ž์‹ ํ…Œ์ด๋ธ”์— ๋ณ„๋„ ์ œ์•ฝ ์กฐ๊ฑด ์„ค์ • ๊ฐ€๋Šฅ

  • NULL ์ปฌ๋Ÿผ ์—†์Œ โ†’ ์ž์‹ ํ•„๋“œ๋Š” ์ž์‹ ํ…Œ์ด๋ธ”์—๋งŒ ์กด์žฌ

๋‹จ์ 
  • ์„ฑ๋Šฅ ์ €ํ•˜ โ†’ JOIN์ด ํ•„์š”ํ•ด ์กฐํšŒ ์„ฑ๋Šฅ ๋Š๋ฆด ์ˆ˜ ์žˆ์Œ

  • ์ฟผ๋ฆฌ ๋ณต์žก โ†’ ํ…Œ์ด๋ธ” ์—ฌ๋Ÿฌ๊ฐœ๋ฅผ JOIN ํ•ด์•ผ ํ•จ.

์˜ˆ์‹œ
-- ๋ถ€๋ชจ ํ…Œ์ด๋ธ”
id | name
---|-----
1  | nabi
2  | choco

-- ์ž์‹ ํ…Œ์ด๋ธ” (cat)
id | sound
---|-------
1  | meow

-- ์ž์‹ ํ…Œ์ด๋ธ” (dog)
id | breed
---|--------
2  | shiba
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
open class Animal(@Id @GeneratedValue val id: Long? = null, val name: String)

@Entity
@Table(name = "cat")
class Cat(val sound: String) : Animal()

@Entity
@Table(name = "dog")
class Dog(val breed: String) : Animal()

TABLE_PER_CLASS (์ž์‹๋ณ„ ํ…Œ์ด๋ธ” ์ „๋žต)

  • ๋ถ€๋ชจ ํด๋ž˜์Šค๋Š” ์‹ค์ œ ํ…Œ์ด๋ธ”์ด ์—†์Œ

  • ์ž์‹ ํด๋ž˜์Šค๋งˆ๋‹ค ๊ฐ์ž ํ…Œ์ด๋ธ” ์ƒ์„ฑ, ๋ถ€๋ชจ์˜ ํ•„๋“œ๋„ ๋ชจ๋‘ ํฌํ•จ

  • ์ฆ‰, ๊ฐ ์ž์‹ ํด๋ž˜์Šค๋Š” ์ž๊ธฐ ํ•„๋“œ๋ฅผ ๋ชจ๋‘ ๊ฐ–๋Š” ๋…๋ฆฝ ํ…Œ์ด๋ธ”

์žฅ์ 
  • JOIN ๋ถˆํ•„์š” โ†’ ์ž์‹ ํ…Œ์ด๋ธ”์— ๋ชจ๋“  ํ•„๋“œ๊ฐ€ ์กด์žฌ

  • ์„ฑ๋Šฅ ๋น ๋ฆ„ (๋‹จ์ผ ์กฐํšŒ ์‹œ) โ†’ ์ฟผ๋ฆฌ๊ฐ€ ๋‹จ์ˆœํ•จ

  • ๊ตฌ์กฐ ๋ช…ํ™• โ†’ ํ…Œ์ด๋ธ” = ํด๋ž˜์Šค ๊ตฌ์กฐ ๊ทธ๋Œ€๋กœ

๋‹จ์ 
  • ์ค‘๋ณต ๋ฐ์ดํ„ฐ โ†’ ๋ถ€๋ชจ ํ•„๋“œ๊ฐ€ ๊ฐ ์ž์‹ ํ…Œ์ด๋ธ”์— ๋ฐ˜๋ณต๋จ

  • ์ „์ฒด ์กฐํšŒ ์–ด๋ ค์›€ โ†’ ์ž์‹๋งˆ๋‹ค UNION์ด ํ•„์š”ํ•จ (SELECT * FROM cat UNION ALL SELECT * FROM dog)

  • ID ์ค‘๋ณต ์ฃผ์˜ โ†’ ๊ฐ ํ…Œ์ด๋ธ”์—์„œ ID ์ถฉ๋Œ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด @GeneratedValue(strategy = TABLE) ๊ถŒ์žฅ

์˜ˆ์‹œ
-- cat ํ…Œ์ด๋ธ”
id | name  | sound
---|-------|-------
1  | nabi  | meow

-- dog ํ…Œ์ด๋ธ”
id | name  | breed
---|-------|--------
2  | choco | shiba
@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
open class Animal(@Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long? = null, val name: String)

@Entity
class Cat(val sound: String) : Animal()

@Entity
class Dog(val breed: String) : Animal()

์ถœ์ฒ˜ : https://docs.jboss.org/hibernate/orm/6.6/migration-guide/migration-guide.html

0
Subscribe to my newsletter

Read articles from ๐™…๐™–๐™™๐™š directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

๐™…๐™–๐™™๐™š
๐™…๐™–๐™™๐™š

Back-end Engineer