본문 바로가기
백엔드(Back-End)/JPA(Java Persistence API)

[JPA] - 객체와 테이블 매핑, 필드와 컬럼 매핑, 기본 키 매핑

by TwoJun 2023. 10. 5.

JPA(Java Persistence API)

 

 

들어가기 앞서 JPA에서 핵심이 되는 중요한 요소

(1) 영속성 컨텍스트(Persistence Context) 이해 

- 동작적인 측면을 이해하는 것

 

(2) 객체와 관계형 데이터베이스의 테이블을 올바르게 매핑하는 것

- 설계적인 측면을 이해하는 것

 

 

 

1. 객체와 테이블 매핑하기

1-1. Entity 매핑의 종류

(1) 객체와 테이블을 매핑하는 것 : @Entity, @Table 

 

(2) 필드와 컬럼을 매핑하는 것 : @Column

 

(3) 기본 키(Primary Key)를 매핑하는 것 : @Id

 

(4) 연관관계 매핑 : @ManyToOne, @JoinColumn 등...

 

 

 

 

1-2. @Entity

(1) 해당 어노테이션이 적용된 객체(클래스)는 JPA가 관리하게 되는 대상 객체인 엔티티(Entity)가 된다.

 

(2) JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 애노테이션이 필수이다.

 

(3) 주의사항

- 기본 생성자(Default constructor)가 필수적이다.

- final class, enum, interface, inner class에선 사용할 수 없다.

- 저장할 필드에 final을 선언하지 않는다.

 

 

 

 

1-3. @Entity의 속성(Attributes)

(1) @Entity엔 기본적으로 name 속성이 존재한다.

@Entity(name = "app_user")
public class User {
    // ...
}

 

(3) 해당 name 속성은 JPA에서 해당 엔티티 클래스의 이름을 지정할 때 사용된다. 기본적으로 해당 속성은 클래스의 이름과 동일하나, name성을 별도로 사용하여 클래스에 대해 사용할 엔티티 이름을 지정할 수 있게 된다.

 

(4) 별도로 name 속성을 정의해 주면 데이터베이스에서 속성값에 정의된 이름을 가진 테이블과 매핑하게 된다. 가급적 같은 클래스의 이름이 없으면 name 속성을 따로 정의하지 않고 기본값을 따라가는 것이 좋다.

 

 

 

 

1-4. @Table

(1) 해당 어노테이션은 JPA에서 엔티티 클래스를 데이터베이스 테이블과 매핑할 때 사용된다.

속성 기능 기본값
name 매핑할 테이블 이름을 지정  엔티티 클래스 이름을 사용
catalog 데이터베이스 catalog와 매핑  
schema 데이터베이스 schema와 매핑  
uniqueConstraints(DDL) DDL 생성 시 Unique 제약 조건 생성  

 

 

 

 

 

 

2. 데이터베이스 schema 자동 생성 (Hibernate DDL 자동 생성 기능)

2-1. 개요

(1) 애플리케이션 로딩 시점에 데이터베이스의 테이블을 설정한 엔티티 클래스의 매핑 정보, 메타데이터를 기반으로 자동으로 생성해 주는 기능을 지원한다.

 

 

 

 

2-2. 특징

(1) 보통 개발 단계에서 테이블을 만들고 객체를 만든다.

- JPA는 그럴 필요가 없다. 객체를 매핑하고 관련 메타데이터를 설정하면 애플리케이션이 로딩되는 시점에 테이블을 자동으로 생성해 준다.

 

(2) 데이터베이스 Dialect를 사용해서 해당 Dialect에 맞는 적절한 DDL을 생성한다.

 

(3) 일반적으로 운영서버에서는 사용하지 않는다. 만약 필요하다면 적절한 튜닝 이후 사용하는 것이 권장된다.

 

 

 

 

2-3. 옵션 : hibernate.hbm2ddl.auto

옵션  설명
create 애플리케이션 로딩 시점에 기존 테이블을 삭제한 후 재생성한다(CREATE + DROP)
create-drop 애플리케이션 로딩 시점에 create와 동일하나 종료 시점에 테이블을 DROP한다.
update 변경된 부분만 부분적으로 적용한다. (운영서버에서는 사용하지 않는다)
validate 엔티티와 테이블이 정상적으로 매핑되었는지 검증한다.
none hibernate.hbm2ddl.auto을 사용하지 않는다.

 

 

 

2-4. 옵션 : hibernate.hbm2ddl.auto 주의사항 

(1) 운영 장비에는 절대 create, create-drop, update 옵션을 사용하지 않는다.

 

(2) 개발 초기 단계에서 create 또는 update를 사용할 수 있다.

 

(3) 테스트 서버에서 update 또는 validate 옵션을 사용한다.

 

(4) 스테이징과 운영 서버에서는 validate 또는 none을 사용한다.

 

 

 

 

2-5. DDL 생성 기능

(1) DDL 생성 기능은 DDL을 자동으로 생성할 때만 사용되고 JPA와 실행 로직에는 영향을 주지 않는다.

 

 

 

 

 

 

3. 필드와 컬럼 매핑하기 

3-1. 필드와 컬럼 매핑, 여러 가지 어노테이션

(1) 컬럼 매핑 : @Column

 

(2) 날짜 타입 매핑 : @Temporal

 

(3) Enum 타입 매핑 : @Enumerated

 

(4) BLOB, CLOB 타입 매핑 : @Lob

 

(5) 특정 필드를 컬럼에 매핑하지 않을 때 : @Transient

 

 

 

 

3-2. @Column 

(1) 해당 어노테이션은 JPA에서 엔티티 클래스의 필드를 데이터베이스 테이블의 컬럼과 매핑할 때 사용하며 @Column을 통해 컬럼의 속성을 정의할 수 있게 된다.

 

 

 

 

3-3. @Column 주요 속성(Attributes) 정리

(1) name

- 데이터베이스 테이블의 컬럼 이름을 지정한다. 기본적으로 엔티티 클래스의 필드 이름이 사용된다.

 

- 기본값 : 엔티티 클래스의 필드 이름

@Column(name = "user_name")
private String username;

 

 

(2) insertable, updatable

- insertable은 해당 컬럼에 대한 INSERT 연산을 허용할지 여부를 지정하고 updatable은 해당 컬럼에 대한 UPDATE 연산을 허용할지 여부를 결정한다. 두 옵션 모두 기본값은 true이다.

@Column(insertable = false)
private Date createdDate;

@Column(updatable = false)
private Date secondCreatedDate;

 

 

(3) nullable
- 해당 컬럼의 값의 null 허용 여부를 결정, 기본값은 true

 

- false로 설정할 경우 DDL 생성 시 not null constraint가 추가된다.

@Column(nullable = false)
private String firstName;

 

 

(4) unique

- 해당 컬럼의 값이 고유해야 하는지 여부를 결정, 기본값은 false

 

- @Table의 uniqueConstraints와 동일하지만 한 컬럼에 간단히 unique constraint를 설정할 때 사용한다.

@Column(unique = true)
private String email;

 

 

(5) columnDefinition

- 데이터베이스에서 컬럼의 정의를 직접 지정 가능하다. 주로 특정 데이터베이스의 특화된 옵션을 사용할 때 유용하다.

@Column(columnDefinition = "VARCHAR(255) DEFAULT 'Guest'")
private String userType;

 

 

(6) length

- 문자열 컬럼의 최대 길이를 지정한다. 기본값은 255 (String 타입에만 사용)

@Column(length = 50)
private String lastName;

 

 

(7) precision, scale

- precision은 숫자 타입의 정밀도를, scale은 숫자 타입의 소수점 이하 자릿수를 지정한다.

 

- 각각 순서대로 기본값은 19, 2

 

- BigDecimal 타입에서 사용하며(BigInteger도 사용 가능) precision은 소수점을 포함한 전체 자릿수를, scale은 소수의 자릿수다. 참고로 double, float 타입에는 적용되지 않으며 아주 큰 숫자나 정밀한 소수를 다뤄야 할 때 사용한다.

@Column(precision = 10, scale = 2)
private BigDecimal salary;

 

 

 

 

3-3. @Enumerated

(1) 자바의 Enum 타입을 매핑할 때 사용한다.

 

(2) 옵션 중에서 @Enumerated(EnumType.ORDINAL)은 사용하지 않는다.

 

(3) 이유는 옵션이 갖는 성질 때문인데 ORDINAL의 경우 enum 데이터의 순서를 데이터베이스에 저장하기 때문에 데이터의 순서가 변경되는 상황에서 사용하게 되면 큰 장애가 발생할 수 있다.

 

(4) 따라서 EnumType.STRING 옵션을 사용한다. STRING의 경우 enum 데이터 자체의 이름을 데이터베이스에 저장하기 때문에 비교적 안전하다.

속성 설명 기본값
value EnumType.ORDINAL 
- enum의 순서를 데이터베이스에 저장

EnumType.STRING
- enum 자체의 이름을 데이터베이스에 저장
EnumType.ORDINAL

 

 

 

 

3-4. @Temporal

(1) 자바의 날짜 타입(java.util.Date, java.util.Calendar)의 필드 데이터를 매핑할 때 사용하는 어노테이션

 

(2) 참고로 LocalDate, LocalDateTime을 사용할 때는 해당 어노테이션을 생략할 수 있다.

(최신 Hibernate가 제공하는 기능이기 때문이다.)

속성 설명 기본값
value @Temporal(TemporalType.DATE)
- 날짜, 데이터베이스 date 타입과 매핑한다.
- ex) 2023-10-05
 
@Temporal(TemporalType.TIME)
- 시간, 데이터베이스 time 타입과 매핑한다.
- ex) 20:28:34
@Temporal(TemporalType.TIMESTAMP)
- 날짜와 시간, 데이터베이스 timestamp 타입과 매핑한다
- ex) 2023-10-05 20:28:34

 

 

 

 

3-5. @Lob

(1) 해당 어노테이션은 JPA에서 데이터베이스에 큰 데이터를 저장할 때 사용한다.

 

(2) 참고로 Lob은 "Large Object"의 약자로 대용량의 데이터를 저장한다는 특징을 가지고 있다.

 

(3) 아래와 같이 크게 2가지 종류의 데이터 타입에 사용된다.

 

(4) CLOB(Character Large Object)

- 텍스트 형식의 대용량 데이터를 저장할 수 있다. 예를 들어, 문자열이나 텍스트 문서 등을 저장할 때 사용

- String, char[], java.sql.CLOB

 

(5) BLOB(Binary Large Object)

- Binary 형식의 대용량 데이터를 저장할 수 있다. 예를 들어, 이미지, 오디오 파일 등을 저장할 때 사용

- byte[], java.sql.BLOB

 

 

 

 

3-6. @Transient

(1) 해당 어노테이션은 JPA에서 특정 필드를 영속화 과정에서 제외할 수 있도록 설정하는 어노테이션이다.

 

(2) 해당 어노테이션이 적용된 필드는 데이터베이스 테이블의 컬럼으로 매핑되지 않는다.

 

(3) 일반적으로 메모리 레벨에서 임시적으로 특정한 값을 보관하고자 할 때 사용한다.

@Entity
public class User {
    @Id @GeneratedValue
    private Long id;
    
    private String username;
    
    @Transient
    private String trasientField;
    
    // Getter, 사용자 정의 메서드 등 생략
}

 

 

 

 

 

 

4. 기본 키(Primary key) 매핑

4-1. 기본 키 매핑 어노테이션

@Entity
public class User {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    // ...
}

(1) @Id

- 엔티티 클래스에서 기본 키(식별자 Primary key, PK)를 지정할 때 사용한다. 해당 어노테이션이 적용된 필드는 엔티티의 식별자로 사용된다.

 

(2) @GeneratedValue

- 필드의 식별자 값이 자동으로 생성되도록 지정하는 어노테이션이다. 데이터베이스가 식별자 값을 자동으로 생성하게 된다.

 

- 정리해 보면, @Id, @GeneratedValue 어노테이션은 모두 엔티티 클래스의 식별자를 정의할 때 사용되는 어노테이션이다.

 

 

 

 

4-2. 기본 키 매핑 방법

(1) 기본 키를 직접 할당하는 경우 : @Id 사용

 

(2) 기본 키를 자동 생성하고자 하는 경우 : @GeneratedValue 사용

 

(3) 기본 키 생성에 있어 @GeneratedValue 어노테이션은 여러 가지 속성 전략이 존재한다.

- @GeneratedValue(strategy = 전략)

속성명 기능
(strategy = GenerationType.IDENTITY) 데이터베이스에서 자동으로 식별자 값을 생성
(strategy = GenerationType.SEQUENCE) 데이터베이스 시퀀스를 적용하여 식별자 값을 생성
- @SequenceGenerator 필요
(strategy = GenerationType.TABLE) 특별한 테이블을 사용하여 식별자 값 생성
- @TableGenerator 필요
(strategy = GenerationType.AUTO) JPA 구현체가 Database dialect에 따라 적절한 전략을 지정해서 생성

 

 

 

 

4-3. @GeneratedValue(strategy = GenerationType.IDENTITY) 특징

(1) 키 생성 전략을 데이터베이스에 위임하는 전략이다.

 

(2) 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용하는 속성이다.

 

(3) JPA는 보통 Transaction commit 시점에 INSERT SQL을 실행한다.

 

(4) MySQL의 AUTO_INCREMENT는 데이터베이스에 INSERT SQL를 실행시킨 이후에 ID 값을 조회할 수 있다.

 

(5) IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL를 실행하고 데이터베이스에서 식별자를 조회할 수 있다.

 

 

 

 

4-4. @GeneratedValue(strategy = GenerationType.SEQUENCE) 특징

(1) 엔티티의 식별자 값을 생성하는 전략 중 하나로, 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다.

(EX) Oracle DB의 sequence

 

(2) 여기서 시퀀스는 일련변호를 생성하는 객체로 주로 데이터베이스에서 사용한다. 시퀀스는 다음 값을 미리 예약해놓고 해당 값을 가져와서 사용하는 방식이다.

 

(3) @SequenceGenerator 어노테이션을 사용해야 한다.

@Entity 
@SequenceGenerator(name = "MEMBER_SEQ_GENERATOR", sequenceName = "MEMBER_SEQ", initialValue = 1, allocationSize = 1) 
public class Member { 
    @Id 
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "MEMBER_SEQ_GENERATOR") 
    private Long id;
    
    //... 
 }

 

 

 

 

4-5. SEQUENCE 전략을 사용하기 위한 어노테이션, @SequenceGenerator

속성 설명 기본값
name 식별자 생성기 이름 지정 필수값
sequenceName 데이터베이스에 등록된 시퀀스 이름 hibernate_sequence
initialValue DDL 생성 시에만 사용되며 시퀀스 DDL을 생성할 때 처음은 1, 시작하는 수를 지정할 수 있다. 1
allocationSize 시퀀스 한 번 호출에 증가하는 수
(성능 최적화에 사용)
데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다.
50
catalog, schema 데이터베이스 catalog, schema의 이름  

 

 

 

 

4-6. @GeneratedValue(strategy = GenerationType.TABLE) 특징

(1) 엔티티의 식별자 값을 생성하는 방법 중 하나로, 데이터베이스에 특정 테이블을 생성하고 해당 테이블을 사용해서 식별자 값을 관리하게 된다.

- 키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략으로 생각할 수 있다.

 

(2) @TableGenerator 어노테이션을 사용해야 한다.

 

(3) 장점 : 모든 데이터베이스에 적용 가능하다.

 

(4) 단점 : 성능 이슈가 존재한다. (테이블을 직접 사용하는 부분에서 발생하는 성능 이슈)

create table MY_SEQUENCES ( 
     sequence_name varchar(255) not null, 
     next_val bigint, 
     primary key ( sequence_name ) 
)

 

@Entity 
@TableGenerator(name = "MEMBER_SEQ_GENERATOR", table = "MY_SEQUENCES", pkColumnValue = "MEMBER_SEQ", allocationSize = 1) 
public class Member { 
     @Id 
     @GeneratedValue(strategy = GenerationType.TABLE, generator = "MEMBER_SEQ_GENERATOR") 
     private Long id;
     
     // ...
}

 

 

 

 

4-7. TABLE 전략을 사용하기 위한 어노테이션, @TableGenerator

속성 설명 기본값
name 식별자 생성기 이름 필수값
table 생성기에 사용될 테이블의 이름을 지정
(키 생성 테이블명)
hibernate_sequences
pkColumnName 테이블에서 생성된 키의 이름을 지정
(시퀀스 컬럼명)
sequence_name
valueColumnName 테이블에서 생성된 키의 값을 저장할 컬럼의 이름을 지정 (시퀀스 값 컬럼명) next_val
pkColumnValue 키로 사용할 값 이름 엔티티의 이름
initialValue 초기값, 마지막으로 생성된 값이 기준 0
allocationSize 시퀀스 한 번 호출에 증가하는 수
(성능 최적화에서 사용)
50
catalog, schema 데이터베이스 catalog, schema 이름  
uniqueConstraint(DDL) Unique constraint를 설정할 수 있다  

 

 

 

 

4-8. 권장되는 식별자 전략

(1) 기본 키 제약 조건

- null이 아니다. 유일하며 변하지 않아야 한다.

 

(2) 이후 미래까지 특정 조건을 만족하는 자연 키는 찾기 어려우므로 대체 키를 사용한다.

 

(3) 예를 들어본다면 주민등록번호도 기본 키로 적절하지 않다.

 

(4) 권장되는 형식

- Long Type 사용, 대체 키 사용, 키 생성 전략 사용

 

 

 

 

 

 

5. 엔티티와 컬럼, 기본 키 매핑 간 주의사항, 데이터 중심 설계의 문제점

5-1. 매핑 시 주의사항 

(1) 일반적으로 엔티티 클래스를 정의할 때 코드 레벨에서 메타 데이터를 별도로 모두 주고 엔티티에 대한 정보를 쉽게 파악할 수 있도록 하는 방법이 한 눈에 도메인을 파악하기 좋다.

 

(2) 보통 Spring Boot와 JPA를 연동해서 프로젝트를 구성한다면 테이블의 컬럼, 엔티티의 필드를 통일시키는 로직을 자동으로 적용해준다.

- 쉽게 풀어보면, Spring Boot가 필드와 컬럼명을 통일시킬 때 기본 설정을 언더 스코어 방식으로 채택했다는 점이다. 데이터베이스 테이블의 컬럼명을 설정할 때 엔티티 필드의 카멜 케이스로 작성된 필드명을 데이터베이스 테이블에서 관례적으로 많이 사용하는 언더 스코어 방식으로 자동으로 매칭해준다.

 

 

 

 

5-2. 데이터 중심 설계의 문제점

(1) 객체는 객체 그래프를 통해 참조를 통한 방식으로 연관된 객체를 적재적소에 찾아갈 수 있어야 한다. 식별자를 통해 찾아가는 것은 좋은 객체지향 설계 방식이 아니다.

 

(2) 예를 들자면, 주문한 고객의 아이디를 찾아야 하는 상황이라고 해 보자. 이럴 때는 고객의 아이디를 주문에서 찾는 것이 아닌 고객 자체의 객체를 참조해서 찾는 것이 좋은 객체지향적 설계라고 볼 수 있다.

 

(3) 식별자를 통해 객체를 탐색하는 것은 관계형 데이터베이스에 초점을 맞춘 설계라고 보면 된다. 이러한 데이터 중심 설계의 문제점을 해결할 수 있는 방법이 다음 포스팅에서 다루게 될 연관관계 매핑에 관한 내용이다.

 

 

 

 

 

6. Reference

※ 해당 포스팅은 InFlearn에서 (전) 우아한형제들, 배달의 민족 서비스 개발팀장(기술이사)으로 재직하셨던 김영한님 "자바 ORM 표준 JPA 프로그래밍(기본편)" 강의를 듣고 공부한 내용을 정리하였습니다.
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

초급자를 위해 준비한 [웹 개발, 백엔드] 강의입니다. JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자

www.inflearn.com

※ 해당 포스팅에 대해 내용 추가가 필요하다고 생각되면 기존 포스팅 내용에 다른 내용이 추가될 수 있습니다.

개인적으로 공부하며 정리한 내용이기에 오타나 틀린 부분이 있을 수 있으며, 이에 대해 댓글로 알려주시면 감사하겠습니다!

댓글