4.1 @Entity

  • @Entity가 붙은 클래스는 JPA가 관리하는 것으로, 엔티티라 부른다.

  • 설정하지 않으면 클래스 이름을 그대로 사용한다.

  • 기본 생성자는 필수다 (파라미터가 없는 public 또는 protected 생성자).

  • final, enum, interface, inner 클래스에는 사용할 수 없다.

  • 저장할 필드에 final을 사용하면 안 된다.

4.2 @Table

SCR-20240829-jhfo

  • @Table은 엔티티와 매핑할 테이블을 지정한다.

4.3 다양한 매핑 사용

  • enum을 사용하려면 @Enumerated 어노테이션으로 매핑해야 한다.
  • 날짜 타입은 @Temporal을 사용해서 매핑한다.
  • @Lob을 사용하면 CLOB, BLOB 타입을 매핑할 수 있다.

4.4 데이터베이스 스키마 자동 생성

JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원한다.

클래스의 매핑정보를 보면 어떤 테이블에 어떤 컬럼을 사용하는지 알 수 있다. JPA는 이 매핑정보데이터베이스 방언을 사용해서 데이터베이스 스키마를 생성한다.

SCR-20240829-jjjd

SCR-20240829-jkaq

4.5 DDL 생성 기능

스키마 자동 생성하기를 통해 만들어지는 DDL에 제약조건을 추가

SCR-20240829-jmhy

SCR-20240829-jmin

유니크 제약조건

SCR-20240829-jmkv

이런 기능들은 단지 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

4.6 기본 키 매핑

[ JPA가 제공하는 데이터베이스 기본 키 생성 전략 ]

  • 직접 할당: 기본 키를 애플리케이션에서 직접 할당한다.

  • 자동 생성: 대리 키 사용 방식

    • IDENTITY: 기본 키 생성을 데이터베이스에 위임한다.

    • SEQUENCE: 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.

    • TABLE: 키 생성 테이블을 사용한다.

4.6.1 기본 키 직접 할당 전략

  • 기본 키를 직접 할당하려면 다음 코드와 같이 @Id로 매핑하면 된다.

  • 기본 키 직접 할당 전략은 em.persist()로 엔티티를 저장하기 전에 애플리케이션에서 기본 키를 직접 할당하는 방법이다.

4.6.2 자동 생성 전략, IDENTITY

SCR-20240829-jolz

  • IDENTITY는 기본 키 생성을 데이터베이스에 위임하는 전략이다. 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다.

  • @Generatedvalue의 strategy 속성 값을 GenerationType.IDENTITY로 지정하면 된다.

SCR-20240829-joxl

SCR-20240829-jpbe

4.6.3. 자동 생성 전략, SEQUENCE

SCR-20240829-jpnz

  • 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트다.
  • SEQUENCE 전략은 이 시퀀스를 사용해서 기본 키를 생성한다.
  • 이 전략은 시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할수 있다.

SCR-20240829-jpvd

SCR-20240829-jqqm

4.6.4. 자동 생성 전략, TABLE

  • TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다.
  • 이 전략은 테이블을 사용하므로 모든 데이터베이스에 적용할 수 있다.

SCR-20240829-jqyn

  • TABLE 전략은 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작방식이 같다.

SCR-20240829-jrml

4.6.5. 자동 생성 전략, AUTO

데이터베이스의 종류도 많고 기본 키를 만드는 방법도 다양하다.

GenerationType .AUTO선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다.

예를 들어 오라클을 선택하면 SBQUENCE를, MySQL을 선택하면 IDENTITY를 사용한다.

  • AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다.
  • 특히 키 생성 전략이 아직 확정되지 않은 개발 초기 단계나 프로토타입 개발 시 편리하게 사용할 수 있다.
  • AUTO를 사용할 때 SEQUENCEL TABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 한다.
    • 스키마 자동 생성 기능을 사용한다면, 하이버네이트가 기본값을 사용해서 적절한 시퀀스나 키 생성용 테이블을 만든다.

4.6.6 기본 키 매핑 정리

  • 직접 할당: em.persist()를 호출하기 전에 애플리케이션에서 직접 식별자 값을 할당해야 한다.

    • 만약 식별자 값이 없으면 예외가 발생한다.
  • IDENTITY: 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후, 영속성 컨텍스트에 저장한다

    • IDENTITY 전략은 테이블에 데이터를 저장해야 식별자 값을 획득할 수 있다.
  • SEQUENCE: 데이터베이스 시퀀스에서 식별자 값을 획득한 후, 영속성 컨텍스트에 저장한다.

  • TABLE: 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후, 영속성 컨텍스트에 저장한다.

[ 권장하는 식별자 선택 전략 ]

데이터베이스 기본 키는 다음 3가지 조건을 모두 만족해야 한다.

  1. null값은 허용하지 않는다.

  2. 유일해야 한다.

  3. 변해선 안 된다.

테이블의 기본 키를 선택하는 전략은 크게 2가지가 있다.

  • 자연 키(natural key)

    • 비즈니스에 의미가 있는 키

    • 예: 주민등록번호, 이메일, 전화번호

  • 대리 키(surrogate key)

    • 비즈니스와 관련 없는 임의로 만들어진 키, 대체 키로도 불린다.

    • 예: 오라클 시퀀스, auto_increment, 키생성 테이블 사용

  • 자연 키보다는 대리 키를 권장

    • 비즈니스 환경은 언젠가 변하기 때문
  • JPA는 모든 엔티티에 일관된 방식으로 대리 키 사용을 권장한다

  • 기본 키는 변하면 안 된다는 기본 원칙으로 인해, 저장된 엔티티의 기본 키 값은 절대 변경하면 안 된다.

    • 이 경우 JPA는 예외를 발생시키거나 정상 동작하지 않는다.
    • setd() 같이 식별자를 수정하는 메소드를 외부에 공개하지 않는 것도 문제를 예방하는 하나의 방법이 될 수 있다.

4.7 필드와 컬럼 매핑: 레퍼런스

SCR-20240829-juro

4.8 기본 매핑 활용 (Kotlin)

요구 사항 분석 및 설계

SCR-20240829-kdip

SCR-20240829-kdha

SCR-20240829-kdli

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// build.gradle.kts에 아래 설정이 있다고 가정
// Kotlin으로 JPA을 쉽게 사용하기 위한 설정

plugins {
    kotlin("plugin.jpa") version "1.9.24"
}

allOpen {
    annotation("jakarta.persistence.Entity")
    annotation("jakarta.persistence.Embeddable")
    annotation("jakarta.persistence.MappedSuperclass")
}

noArg {
	annotation("jakarta.persistence.Entity")
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package ~~~~~
import jakarta.persistence.*
import java.util.Date

@Entity
class Member(
    name: String,
    street: String,
    zipcode: String
) {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    var id: Long? = null
        protected set
    var name = name
        protected set
    var street = street
        protected set
    var zipcode = zipcode
        protected set
}

enum class OrderStatus {
    ORDER, CANCEL
}

@Entity
@Table(name = "ORDERS")
class Order(
    orderDate: Date,
    status: OrderStatus
) {
    @Id
    @GeneratedValue
    @Column(name = "ORDER_ID")
    var id: Long? = null
        protected set

    @Column(name = "MEMBER_ID")
    var memberId: Long? = null
        protected set

    @Temporal(TemporalType.TIMESTAMP)
    var orderDate: Date = orderDate
        protected set

    @Enumerated(EnumType.STRING)
    var status: OrderStatus = status
        protected set
}

@Entity
class OrderItem(
    orderPrice: Int,
    count: Int
) {
    @Id
    @GeneratedValue
    @Column(name = "ORDER_ITEM_ID")
    var id: Long? = null
        protected set

    @Column(name = "ITEM_ID")
    var itemID: Long? = null
        protected set

    @Column(name = "ORDER_ID")
    var orderId: Long? = null
        protected set

    var orderPrice: Int = orderPrice
        protected set

    var count: Int = count
        protected set
}

@Entity
class Item(
    name: String,
    price: Int,
    stockQuantity: Int
) {
    @Id
    @GeneratedValue
    @Column(name = "ITEM_ID")
    var id: Long? = null

    var name: String = name
        protected set

    var price: Int = price
        protected set

    var stockQuantity: Int = stockQuantity
        protected set
}

Reference