본문 바로가기
JPA/자바 ORM 표준 JPA 프로그래밍 - 기본편

[JPA] 엔티티 매핑

by hk27 2022. 2. 3.

안녕하세요. 

오늘은 엔티티와 테이블을 매핑하는 방법을 알아보겠습니다. 

 

객체와 테이블 매핑

@Entity

JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 붙여야 합니다.

@Entity가 붙은 클래스는 JPA가 관리하고, 엔티티라고 부릅니다.

참고로 '엔티티'라는 단어는 @Entity가 붙어서 JPA가 관리하는 클래스를 의미하기도 하고,

'엔티티 = 개체 = 레코드 = 행'으로 한 객체를 의미하기도 합니다[1][2]. 

여기서는 전자의 JPA가 관리하는 클래스라는 의미로 사용하겠습니다. 

 

@Entity 적용 시 주의 사항은 아래와 같습니다.

1. 기본 생성자는 필수입니다.

파라미터가 없는 public 또는 protected 생성자가 필요합니다. 

 

이유가 궁금해서 인프런의 질문 답변 게시판을 찾다가, 깊은 내용이 있음을 알게 되어서 공유합니다. 

JPA 구현체들은 엔티티를 내부에서 다양한 방식으로 사용합니다[3].

예를 들어서 엔티티를 JPA 구현체는 리플렉션을 사용해서 객체를 먼저 생성하고, 나중에 값을 필드에 직접 넣어주기도 합니다. 지연 로딩 등을 위해 프록시 기술을 사용하기도 합니다.

 

풀어서 설명하자면, JPA는 find 결과 엔티티를 만들 때 기본 생성자로 객체를 생성한 후 리플렉션을 사용해서 DB의 데이터를 객체 필드에 주입합니다[4]. 

또한 프록시 객체 클래스는 실제 객체 클래스를 상속받아서 부모의 기본 생성자를 사용합니다[5] . 

 

정적 팩토리 메서드 방식을 사용할 때 등 의도적으로 생성자를 가리고 싶을 때가 있습니다. 

그럴 때는 protected로 생성자를 지정해서 패키지가 다르고 하위 클래스도 아닌 클래스에서 생성자를 부르는 것을 막을 수 있습니다. 

 

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

생성자가 없거나 상속이 불가능하면 위에서 설명했듯이 엔티티로 지정할 수 없습니다. 

 

3. 저장할 필드에 final을 사용하면 안 됩니다.

기본 생성자로 객체를 생성해야 하므로 final 필드가 있으면 안 됩니다. 또한 final 객체는 일반적으로 DB에 저장될 필요도 없을 것입니다. 

 

@Entity 어노테이션은 아래 코드처럼 사용합니다.

@Entity // @Entity(name = "Member")
@Table(name = "MEMBER")
public class Member {
    @Id
    private Long id;

    private String username;
    private Integer age;

}

JPA에 등록해서 사용할 클래스에 @Entity를 붙입니다.

속성으로 name을 지정할 수 있고, JPA에서 사용할 엔티티의 이름을 지정합니다. 

기본값은 클래스 이름을 그대로 사용하는 것이며, 보통 기본값을 그대로 사용합니다. 

 

 

@Table

위에서 본 @Entity 어노테이션은 필수이지만 @Table 어노테이션은 필수가 아닙니다. 

Table은 단어 그대로 엔티티와 매핑할 DB의 테이블을 의미합니다. 

보통 @Table(name="MEMBER")과 같이 테이블의 이름을 지정하기 위해 사용하고, 생략하면 엔티티 이름을 그대로 테이블 이름으로 사용합니다. 

@Table(uniqueContraints={})로 유니크 제약조건을 만들기도 합니다. 이 기능은 스키마 자동 생성 기능을 사용해서 DDL을 만들 때만 사용됩니다. 스키마 자동 생성 기능은 게시글의 후반부에서 자세히 알아보겠습니다.

@Table(uniqueContraints={})을 어떻게 사용하는지 예시를 봅시다. 

@Getter @Setter
@Entity
@Table(name = "MEMBER",
        uniqueConstraints = {
            @UniqueConstraint(
                name = "NAME_AGE_UNIQUE", // 생략 가능
                 columnNames = {"username", "AGE"}
)})
public class Member {
    @Id
    private Long id;
    private String username;

    @Column(name="AGE")
    private Integer age;
}

여러 개 객체가 동시에 겹치지 않도록 설정합니다.

예를 들어서 위의 예제 코드에서 제약 조건의 이름은 "NAME_AGE_UNIQUE"이며, 

적용되는 컬럼은 "username"과 "AGE"입니다. 참고로 이와 같이 컬럼을 하나는 대문자, 하나는 소문자로 일관성 없게 지정하는 방식은 바람직하지 않으며 저는 필드의 이름이 아닌 컬럼의 이름으로 속성을 작성해야 함을 나타내기 위해 고의로 이렇게 예시를 만들었습니다.

적용되는 컬럼이 중요합니다. 여기서는 이름과 나이가 둘 다 같으면 안되는 제약조건 입니다.

예를 들어서 회원1(20세)과 회원2(101세)는 나이는 같지만 이름이 다르므로 동시에 DB에 넣을 수 있습니다. 

 

그러나 두 회원이 회원1(20세), 회원1(20세)로 이름과 나이가 동시에 같으면 에러가 납니다.

ERROR: Unique index or primary key violation: "PUBLIC.NAME_AGE_UNIQUE_INDEX_8 ON PUBLIC.MEMBER(USERNAME, AGE) VALUES 1"; SQL statement:

"NAME_AGE_UNIQUE" 제약 조건에 어긋난다고 에러가 나며, 데이터가 반영되지 않습니다. 

참고로 각각 컬럼에 대해서도 유니크 제약 조건을 걸 수 있고, @Column 어노테이션의 unique 속성을 이용합니다. 잠시 후 다루겠습니다. 

 

필드와 컬럼 매핑

필드에 아무 어노테이션도 지정하지 않으면 자동으로 필드명으로 컬럼이 만들어집니다.

그러나 어노테이션을 추가해서 정보를 추가할 수 있습니다.

필드와 컬럼 매핑에 사용되는 어노테이션은 아래와 같습니다.

어노테이션 설명
@Column 컬럼 매핑
@Temporal 날짜 타입 매핑
@Enumerated enum 타입 매핑
@Lob BLOB, CLOB 매핑
@Transient 특정 필드를 컬럼에 매핑하지 않음(매핑 무시)

하나씩 알아봅시다.

 

@Column

컬럼을 매핑하는 어노테이션입니다.

사용 가능한 속성은 아래 표와 같습니다. 

속성 설명 기본값
name 필드와 매핑할 테이블의 컬럼 이름 객체의 필드 이름
insertable, updatable 등록, 변경 가능 여부 true
nullable(DDL) null 값의 허용 여부 설정. false로 설정하면 DDL 설정 시에 not null 제약 조건이 붙음 true
unique(DDL) 유니크 제약 조건 false
columnDefinition(DDL) 데이터베이스 컬럼 정보 직접 설정
ex) "varchar(10) default 'EMPTY'"
필드의 자바 타입, 방언 정보 사용해 만듦
length(DDL) 문자 길이 제약조건, String 타입에만 사용 255
precision, scale(DDL) BigDecimal, BigInteger 타입에서 사용.
precision은 소수점 포함 전체 자릿수, scale은 소수 부분의 자릿수. 아주 큰 숫자나 정밀한 소수를 다룰 때 사용
precision=19, sclae=2

name, nullable이 주로 사용됩니다. 

"(DDL)"이 있는 속성은 스키마 자동 생성 기능을 사용해서 DDL을 만들 때만 사용됩니다. 애플리케이션 동작 과정에서 JPA가 고려하지 않습니다.

 

@Enumerated

자바 enum타입을 매핑할 때 사용합니다.

중요한 것이 있는데, enum타입을 매핑할 때는 반드시 @Enumerated(EnumType.STRING)을 명시해야 합니다.

value 값으로 EnumType.ORDINAL과 EnumTYPE.STRING을 지정할 수 있는데 ORDINAL은 enum의 순서를 DB에 저장하고 STRING은 enum의 이름을 저장합니다. 애플리케이션을 만들면서 enum을 삭제하거나 추가하면 순서가 바뀔 수 있고, 큰 문제가 발생할 수 있기 때문에 반드시 STRING으로 지정합니다. 

 

@Temporal

날짜 타입(Date, Calender)을 매핑할 때 사용하는데, LocalDate, LocalDateTime을 사용할 때는 생략 가능합니다.

속성 설명 기본값
value TemporalType.DATE: 날짜,
데이터베이스 date 타입과 매핑(예: 2022-02-03)
TemporalType.TIME: 시간,
데이터베이스 time 타입과 매핑(예: 11:11:11)
TemporalType.TIMESTAMP: 날짜와 시간,
데이터베이스 timestamp 타입과 매핑(예: 2022-02-03 11:11:11)
필수 지정

 

@Lob

varchar가 넘는 큰 컨텐츠에 사용됩니다. @Lob에는 지정할 수 있는 속성이 없습니다.

매핑한 필드 타입이 문자면 DB의 CLOB 타입으로 매핑되고, 나머지는 BLOB 타입으로 매핑됩니다. 

 

@Transient

@Transient 어노테이션을 붙인 필드는 컬럼에 매핑되지 않습니다.

주로 메모리상에서만 임시로 어떤 값을 보관하고 싶을 때만 사용합니다.  

 

예를 들어서 아래 코드처럼 사용할 수 있습니다.

@Entity
public class Member {
    @Id
    private Long id;

    @Column(name="name", nullable=false, length=10)
    private String username;

    @Column(unique = true)
    private Integer age;

    @Enumerated(EnumType.STRING)
    private MemberStatus status;

    private LocalDateTime updateDate;

    @Lob
    private String description;

    @Transient
    private Integer temp;
}

 

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

DDL을 애플리케이션 실행 시점에 자동으로 생성해주는 기능입니다.

persistence.xml 파일에 아래와 같이 지정하여 사용합니다. (지정하지 않으면 기본값은 none으로, 자동 생성 기능을 사용하지 않습니다.)

<property name="hibernate.hbm2ddl.auto" value="create" />
옵션 설명
create 기존 테이블 삭제 후 다시 생성 (DROP + CREATE)
create-drop create와 같으나 종료 시점에 테이블 DROP (DROP + CREATE + DROP)
update DB 테이블과 엔티티 매핑정보를 비교해 변경 사항만 수정
validate DB 테이블과 엔티티 매핑정보를 비교해 차이가 있으면 경고를 남기고 애플리케이션을 실행하지 않음. 
none 자동 생성 기능 사용하지 않음

 

개발 초기에 create를 사용하면 자동으로 DDL이 생성되므로 편리하지만, 운영 서버에서는 DDL을 바꾸는 create, create-drop, update를 사용하면 안 됩니다. 

개발 환경에 따른 추천 전략은 다음과 같습니다.

- 개발 초기 단계: create, update

- 초기 단계 테스트: create, create-drop

- 테스트 서버: update, validate

- 운영 서버: validate, none

 

 

이번 게시글에서는 엔티티 매핑에 대해서 알아보았습니다.

다음 게시글에서는 기본키 매핑에 대해 알아보겠습니다.

이어지는 내용이니 관심 있는 분은 아래 게시글을 참고해주세요.

[JPA] 기본 키 매핑

 

 

인프런  '자바 ORM 표준 JPA 프로그래밍 - 기본편' 강의를 듣고 공부하며 정리한 자료입니다. 

잘못된 부분은 피드백 주시면 감사하겠습니다. 

글 읽어주셔서 감사합니다 :-)

 

참고 자료

자바 ORM 표준 JPA 프로그래밍 - 기본편 섹션 4. 엔티티 매핑, https://www.inflearn.com/course/ORM-JPA-Basic

김영한, 자바 ORM 표준 JPA 프로그래밍, 에이콘출판(2015), pp.122-131, 145-151.

[1] https://ko.wikipedia.org/wiki/%EA%B0%9C%EC%B2%B4_(%EC%BB%B4%ED%93%A8%ED%8C%85)

[2] http://wiki.hash.kr/index.php/%EB%A0%88%EC%BD%94%EB%93%9C

[3] https://www.inflearn.com/questions/164425

[4] https://hyeonic.tistory.com/191

[5] https://wbluke.tistory.com/6

 

 

댓글