본문 바로가기
Framework and Tool/JPA

JPA - 엔티티 맵핑 - 기본

by ocwokocw 2021. 6. 27.

- 참조: 자바 ORM 표준 JPA 프로그래밍

- 참조: https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/domain/naming.html

- 엔티티 맵핑

JPA 를 사용하는데 있어서 가장 중요한일은 테이블과 엔티티를 정확히 맵핑하는것이다. JPA 의 맵핑 어노테이션은 아래와 같다.

  • 객체 - 테이블 맵핑: @Entity, @Table
  • 기본 키 맵핑: @Id
  • 필드와 컬럼 맵핑: @Column
  • 연관관계 맵핑: @ManyToOne, @JoinColumn

- @Entity

JPA 를 사용해서 맵핑한 클래스는 @Entity 어노테이션을 반드시 붙여야 한다. 그래야 JPA 가 관리해야 하는 클래스라고 인식할 수 있다. @Entity 적용할때에는 주의사항이 몇 가지 있다.

 

기본 생성자가 필수이다. Class 에서 생성자 코드를 명시하지 않으면 Java 가 기본 생성자를 자동으로 생성하지만, 인자와 함께 생성자를 정의했다면 기본 생성자를 자동으로 생성하지 않는다. 이 때에는 기본 생성자를 직접 생성해주어야 한다.

 

final 클래스, enum, interface, inner 클래스에는 사용할 수 없다. 또 지정할 필드에 final을 사용하면 안된다.


- @Table

엔티티와 맵핑할 테이블 이름을 정의한다. 생략하면 맵핑한 엔티티 이름을 테이블 이름으로 사용한다.


- 맵핑 예제

우리가 사용하고 있던 Member 테이블에 아래와 같은 요구사항이 추가된다고 가정해보자.

  • 회원 Role 추가 (일반, ADMIN)
  • 회원 가입일과 수정일 추가
  • 회원 Description 필드 추가, 길이 제한 X

위의 요구사항을 만족하도록 Member 엔티티 클래스를 수정해보자.

 

@Entity
@Table(name = "MEMBER")
public class Member {

	@Id
	@Column(name = "ID")
	private String id;
	
	@Column(name = "NAME")
	private String userName;
	
	private int age;

	@Enumerated(EnumType.STRING)
	private RoleType roleType;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date createDate;
	
	@Temporal(TemporalType.TIMESTAMP)
	private Date lastModifiedDate;

	@Lob
	private String description;

 

  • roleType: 자바의 Enum 형을 사용해서 회원 타입을 추가하였다. Enum 형을 사용할 때에는 @Enumerated 어노테이션을 맵핑한다. EnumType 은 String 이외에 EnumType.ORDINAL (Persist enumerated type property or field as an integer.) 이 있다.
  • createDate, lastModifiedDate: 날짜 타입은 @Temporal 어노테이션을 사용한다. DATE, TIME, TIMESTAMP 형이 있다.
  • description: 길이제한이 없는 요구조건이 있으므로 LOB 을 사용한다. Java 자료형에 따라 CLOB, BLOB 이 자동으로 결정된다.

- 스키마 자동 생성

프로젝트 초기 작성시 H2 서버를 띄울 때 MEMBER 테이블 생성 스크립트를 먼저 적용한 후, Member 엔티티 클래스를 만들어서 어플리케이션을 구동하였다.

 

JPA 는 맵핑정보와 방언을 사용해서 DB 스키마를 자동으로 생성하는 기능을 지원한다. 이 기능을 사용하기 위해 persistence.xml 옵션을 건드려보자.

 

<property name="hibernate.hbm2ddl.auto" value="create" />

 

위의 옵션을 주고 실행하면 아래와 같은 SQL 이 나타난다. 우리가 추가한 속성들(roleType, createDate, lastModifiedDate, description)의 Java 형에 따라서 DB 컬럼형이 자동으로 정해진다. 매번 속성이 추가됨에 따라 DB 에 컬럼을 추가하고, Mybatis 에서 SQL 들을 모두 수정하는 작업들을 하지 않아도 된다.

 

Hibernate: 
    
    drop table if exists MEMBER CASCADE 
10:52:38.321 [main] INFO org.hibernate.orm.connections.access - HHH10001501: Connection obtained from JdbcConnectionAccess [org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess@3a71c100] for (non-JTA) DDL execution was not in auto-commit mode; the Connection 'local transaction' will be committed and the Connection will be set into auto-commit mode.
10:52:38.339 [main] DEBUG org.hibernate.SQL - 
    
    create table MEMBER (
       ID varchar(255) not null,
        age integer not null,
        createDate timestamp,
        description clob,
        lastModifiedDate timestamp,
        roleType varchar(255),
        NAME varchar(255),
        primary key (ID)
    )
Hibernate: 
    
    create table MEMBER (
       ID varchar(255) not null,
        age integer not null,
        createDate timestamp,
        description clob,
        lastModifiedDate timestamp,
        roleType varchar(255),
        NAME varchar(255),
        primary key (ID)
    )

 

하지만 자동으로 생성해서 실행하는 DDL 기능이 완벽하진 않으므로, 운영환경에서 옵션을 사용하는것을 추천하지는 않는다.

 

위의 DDL 스크립트를 보면 테이블을 drop 한 후 생성하는데, Tomcat 을 내리고 올릴때마다 모든 테이블을 DROP 후 CREATE 하면 상당히 번거롭다. 아래를 참조하여 프로젝트 상황이나 적용되는 서버가 Local, 개발, Staging, 운영인지 여부에 따라 적절한 hibernate.hbm2ddl.auto 옵션을 사용하면 되겠다.

  • create: 예제에서 사용한 옵션으로 어플리케이션을 기동할 때 DROP + CREATE 을 수행한다.
  • create-drop: create 옵션의 동작과 더불어, 어플리케이션을 종료할 때 DROP 을 추가 수행한다. (DROP + CREATE + DROP)
  • update: DB 테이블과 엔티티 맵핑정보를 비교하여 변경사항만 수정한다.
  • validate: DB 테이블과 엔티티 맵핑정보를 비교해서 차이가 있으면 경고를 남기고 애플리케이션을 실행하지 않는다.
  • none: 유효하지 않은 옵션값이다. 속성에 유효하지 않은 옵션값을 설정하면 자동 생성 기능을 이용하지 않는것으로 간주한다.

일반적으로 DB 테이블에서는 언더스코어 룰을 사용하는데, 위에 생성된 DDL 에는 카멜케이스로 생성된다. hibernate 에서는 이를 자동으로 변경할 수 있다. 책의 예제는 hibernate.ejb.naming_strategy 옵션을 되어있는데, hibernate5 에서는 아래 옵션으로 수행해야 한다.

 

<property name="hibernate.physical_naming_strategy" 
            	value="org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"/>

 

persistence.xml 에 위의 옵션을 추가하고, 수행하면 DDL script 가 언더스코어 룰로 나타난다.

 

create table member (
       id varchar(255) not null,
        age integer not null,
        create_date timestamp,
        description clob,
        last_modified_date timestamp,
        role_type varchar(255),
        name varchar(255),
        primary key (id)
    )
Hibernate: 
    
    create table member (
       id varchar(255) not null,
        age integer not null,
        create_date timestamp,
        description clob,
        last_modified_date timestamp,
        role_type varchar(255),
        name varchar(255),
        primary key (id)
    )

 

hibernate 의 이름 맵핑 전략은 implicit(hibernate.implicit_naming_strategy) 과 예제에서 사용한 physical 2 단계로 나누어져 있다. implicit 은 @Column 을 사용해서 명시적으로 어노테이션을 주지 않은 필드에 대해 이름 전략을 적용한다. 자세한 내용은 https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/domain/naming.html 를 참조하자.


- DDL 생성 기능

한 가지 요구 조건을 더 추가해보도록 하자. 이름은 필수로 입력되어야 하고, 10 자리까지만 허용한다. Application 로직으로 관리할수도 있지만 Table 에 제약조건을 주는 경우도 있다. Member 의 userName 을 아래와 같이 수정해보자.

 

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

 

Log 에 출력된 DDL 에 아래처럼 name 컬럼에 not null 이 추가되고 자리수도 10 으로 제한되었다.

 

Hibernate: 
    
    create table member (
       id varchar(255) not null,
        age integer not null,
        create_date timestamp,
        description clob,
        last_modified_date timestamp,
        role_type varchar(255),
        name varchar(10) not null,
        primary key (id)
    )

 

만약 특정 필드들이 Unique 해야한다는 제약조건이 있다면 어떻게 할까? @Table 수준에도 제약조건을 추가할 수 있다.

 

@Table(name = "MEMBER", 
	uniqueConstraints = {
		@UniqueConstraint(name="NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"})
	})

===== 출력 Log
Hibernate: 
    
    alter table member 
       add constraint NAME_AGE_UNIQUE unique (name, age)

 

위의 제약조건과 같은 것들은 DDL 자동생성 기능을 이용하지 않고 직접 생성하면 사실 필요없는 기능이다. 하지만 그럼에도 달아주는 경우가 있는데, 어노테이션을 보면 해당 필드의 제약조건들을 알 수 있기 때문이다. 또 프로젝트 특성에 따라 어노테이션 내용에 기반한 공통 Validator 와 같은 기능도 구현가능하다.

댓글