JPA

[JPA] 배워서 엔터프라이즈 개발하자 4일차 - 엔티티 매핑

suhwanc 2022. 3. 12. 03:33

본 글은 김영한님의 [인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편] 강의와 자바 ORM 표준 JPA 프로그래밍 책을 기반으로 정리해 작성하였습니다.

자바 ORM 표준 JPA 프로그래밍 책

 

 

오늘은 JPA를 사용하면서 가장 기본적이고 중요한 일중 하나인 엔티티와 테이블 매핑, 기본 키(PK) 매핑, 필드와 컬럼 매핑에 대해서 알아볼 것이다.

 

 

1. @Entity


JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 를 필수로 붙여야 한다. @Entity가 붙은 클래스는 JPA가 관리하는 것으로, 엔티티라 부른다.

@Entity
public class suhwanc {
    private int age;
}

 

@Entity를 사용할 때는 주의할 점이 몇 가지 있다.

 

  • 기본 생성자가 필수이다(파라미터가 없는 public or protected 생성자)
  • 저장할 필드에 final을 사용하면 안 된다.
  • 클래스가 final, enum, interface, inner 인 경우에는 사용할 수 없다.

 

특히 기본 생성자가 필수인 것은 필자도 공부하면서 알게되었는데, 이게 모르고 그냥 사용할 때는 @Entity가 붙여진 클래스의 경우 JPA가 기본 생성자를 만들어주기 때문에 필수라는 것을 인지하지 못한다.

하지만 만약 우리가 임의로 생성자를 하나 만들게 되면 따로 기본 생성자를 하나 꼭 만들어줘야 한다.

 

 

2. @Table


@Table은 엔티티와 매핑할 테이블을 지정한다. 생략 시 테이블이 만들어지나, 엔티티 이름을 테이블 이름으로 사용한다.

@Entity
@Table(name="suhwan")
public class suhwanc{
    private int age;
}

 

보통 이런식으로 name 속성을 붙여 테이블 이름을 명시해준다.

필자는 name 속성을 안붙이다가 호되게 당한 적이 있는데, class 이름을 Group이라 지정했다가 나중에 SQL Group By 명령어랑 겹쳐서 테이블 생성이 안되는지도 모르고 몇 시간을 고민한 경험이 있다 ㅠㅠ

이미 데이터베이스를 많이 다루신 분들은 아시겠지만, 저처럼 초보 개발자들은 name 속성을 적용해주는게 좋을 것 같다.

 

이 밖에도 catalog, schema, uniqueConstraints 속성이 있다. (공식 문서 참조)

 

 

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


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

즉, 위에서 우리가 선언한 엔티티들을 단번에 만들어 놓는다는 것이다.

 

스키마의 자동 생성 기능을 사용하려면 persistence.xml에 다음 속성을 추가하면 된다.

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

 

이 속성을 추가하면 애플리케이션 실행 시점에 데이터베이스 테이블을 자동으로 생성한다.

그리고 value는 create 말고도 여러 종류가 있는데, 꼭 알아둬야한다.

 

  • create : 기존 테이블을 삭제하고 새로 생성한다. (DROP + CREATE)
  • create - drop : create 기능 후에 drop을 한다. (DROP + CREATE + DROP)
  • update : 데이터베이스 테이블과 엔티티 매핑정보를 비교해서 변경사항만 수정한다.
  • validate : update처럼 비교하지만, 경고를 남기고 애플리케이션을 실행하지 않는다. (수정 x)
  • none : 아무것도 하지 않는다. (속성 자체를 삭제하면 된다.)

 

꼭 알아둬야할 주의사항은, 운영 서버에서는 절대 create, update 등을 쓰면 안된다. 오직 none 뿐이다.

 

또한 다음 기능을 이용하면 콘솔에 DDL을 출력할 수 있다. (이거 정말 좋은 기능이다. 꼭 설정해두자.)

(DDL, 데이터 정의어는 테이블과 같은 데이터 구조를 정의하는 데 쓰이는 명령어로 CREATE, DROP 등이 있다.)

<property name="hibernate.show_sql" value="true" />

 

 

4. DDL 제약 조건 추가하기


테이블 또는 컬럼을 추가하는 DDL을 작성할 때 JPA도 SQL과 마찬가지로 몇 가지 제약 조건을 추가할 수 있다. 

 

  • nullable : @Column의 매핑 속성 중 하나로, false로 설정 시 not null 제약조건을 추가할 수 있다.
  • length : @Column의 매핑 속성 중 하나로, 컬럼의 문자 크기를 지정할 수 있다.
  • uniqueConstraints : @Table의 매핑 속성 중 하나로, 유니크 제약조건을 추가할 수 있다.

 

5. 기본 키 매핑


테이블의 기본 키를 생성하는 방법은 이전에 배운 것처럼 우리가 @Id 어노테이션을 붙인 후 임의의 값을 넣어도 되지만 서비스가 앞으로 어떻게 바뀔지 모르기때문에 웬만하면 자동으로 생성해주는 키(대리 키)를 권장한다고 한다.

당연히 JPA도 제공하는 데이터베이스 기본 키 생성 기능이 있는데 다음과 같다.

 

  • auto (디폴트) : 선택한 데이터베이스 방언(DBMS 공급 업체)에 따라 아래 전략들 중 하나를 자동으로 선택한다. 예를 들어 오라클은 sequence, mysql은 identity를 사용한다는 것이다.
  • identity : 기본 키 생성을 데이터베이스에 위임한다.
  • sequence : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.
  • table : 키 생성 테이블을 사용한다.

 

1) identity 전략

 

DB에 Id(PK) 값만 Null로 넣게되면, DB가 이를 인식하고 데이터베이스가 순서대로 값을 채워준다. 그야말로 생성을 위임한다는 것이다.

이 전략은 일단 DB에 넣어야 기본 키 값이 정해지기 때문에 값을 저장하고 나서야 기본 키 값을 구할 수 있다.

 

* 최적화 방법

JDBC3에 추가된 Statement.getGeneratedKeys() 메서드를 사용하면 데이터를 저장하면서 동시에 생성된 기본 키 값을 얻어 올 수 있어 하이버네이트는 이 메서드를 사용해 데이터베이스와 한 번만 통신할 수 있게 최적화가 가능하다고 한다.

 

* 주의

엔티티가 영속 상태가 되기 위해선 pk값이 필수적이다. 하지만 이 전략은 DB에 저장한 후에야 알 수 있으니 쓰기 지연 전략이 통하지 않는다.

 

 

2) sequence 전략

 

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트이다.

sequence 전략은 이 시퀀스를 사용해서 키본 키를 생성한다. 기본적으로 시퀀스를 만들어야 하는데, 사용 코드는 다음과 같다.

 

@Entity
@SequenceGenerator(
	name = "seq_generator",
    sequenceName = "seq",
    initialValue = 1, allocationSize = 1)

public class Suhwanc {
	
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
    				generator = "seq_generator")
    private Long id;
}

 

우선 데이터베이스 시퀀스를 매핑해야 한다.

위 코드에서는 seq_generator라는 시퀀스 생성기를 먼저 등록한 후에 pk로 사용할 컬럼에 키 생성 전략을 다음과 같이 설정하고 시퀀스 생성기를 할당해주었다.

 

sequence 전략은 em.persist()를 호출해 엔티티를 저장할 때, identity 전략과 달리 DB에 직접 들어가는 것이 아니라 데이터베이스 시퀀스를 사용해 식별자를 조회하게 된다. 조회한 식별자를 엔티티에 할당한 후에 엔티티를 영속성 컨텍스트에 저장한다.(insert 쿼리가 안나간다는 의미이다.)

 

* @SequenceGenerator 의 allocationSize 속성

이 속성은 시퀀스 한 번 호출에 증가하는 수이며, 기본값이 50으로 설정되어있는데, 이유는 다음과 같다.

 

- allocateSize 의 값이 1인 경우, 매번 persist 할 때 마다, sequence.nextVal 로 DB 를 호출하게 되어 성능 이슈가 존재한다.
- 기본 설정으로 allocateSize 가 50 이면, DB 에는 미리 50을 올려놓고, 메모리상에서 1씩 올려서 50까지 사용한다.
- 만약 50까지 다사용했다면 다시 호출하여 DB의 값은 100으로 올려 이전 과정을 반복한다.
- 이 방법을 사용하면 다수 WAS 를 사용해도 동시성 문제가 해결된다.

 

(참고 : https://ncucu.me/191)

 

 

3) table 전략

 

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

이 전략은 테이블을 사용하기 때문에 모든 데이터베이스에 적용할 수 있다.

 

@Entity
@TableGenerator(
name = "seq_generator",
table = "seq",
pkColumnValue = "id_seq", allocationSize = 1)

public class Suhwanc {
	
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
    	generator = "seq_generator")
    private Long id;
}

 

우선 @TableGenerator를 사용해 테이블 키 생성기를 등록한 후, 사용할 pk 컬럼에서 전략을 등록하고, 생성기를 지정해주면 된다.

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

 

 

6. 필드와 컬럼 매핑


이번엔 컬럼 매핑용 어노테이션들을 간단하게만 정리하고 넘어가겠다.

 

1) @Enumerated - 자바의 enum 타입을 매핑할 때 사용한다.

 

속성에는 value가 있는데, ORDINAL과 STRING으로 나뉜다.

ORDINAL은 enum의 순서, 즉 1,2,3,... 를 데이터베이스에 저장하고, STRING은 enum 이름 자체를 저장하는 기능을 한다.

디폴트 값은 ORDINAL 이지만, enum이 갑자기 중간에 하나 추가되서 순서가 뒤바뀐다면 문제가 생기므로 STRING을 사용하는 것을 권장한다고 한다.

 

2) @Temporal - 날짜 타입을 매핑할 때 사용한다.

 

필자는 최근에 그냥 Java의 LocalDateTime 이라는 자바 8 이후부터 쓸 수 있는 로컬 데이트 타입을 사용하긴 했는데

@Temporal 어노테이션도 그와 마찬가지로 날짜, 시간을 매핑할 때 사용된다.

속성으로는 DATE, TIME, TIMESTAMP가 존재한다.

 

3) @Lob - 데이터베이스 BLOB, CLOB 타입과 매핑한다.

 

데이터베이스를 공부하지 않으신 분들은 잘 모르실 것 같은데, 간단히 말하면 매우 큰 데이터들을 저장하는 타입이라 보시면 된다.

여기서 CLOB이 문자들(String, char)을 저장하며 BLOB은 이진 대형 객체를 저장한다.

 

4) @Transient

 

이 필드는 따로 테이블에 매핑하지 않는다. 메모리 쪽에서 무언가 테스트하고 싶은데 변수 값이 필요하다면 사용한다고 한다.

 

 

오늘은 엔티티의 여러 매핑에 대해 알아보았다. 내일은 연관관계가 있는 엔티티들은 어떻게 매핑하는지 알아보도록 하겠다. 

 

* 잼민이의 질문 모음

(정리하면서 생긴 궁금증과 책에 있는 Q&A 내용들을 정리해두었습니다.)

 

Q1. 운영 서버에서는 hibernate.hbm2ddl.auto 속성을 반드시 none으로 해야한다는데, 그럼 데이터베이스 구조 못 바꾸나요?

A1. 그건 아니란다. 운영 서버에서 auto를 쓰게 된다면 만약 우리가 어떤 부분을 고쳤을 때 어떤 다른 부분이 변경될 지 예측하기가 어려워지기 때문이지. 따라서 수정할 때는 스키마 자동 생성 기능을 쓰지 않고, 우리가 직접 SQL 스크립트를 작성하는 것이 좋단다!