본문 바로가기
Back-End/JPA

[북스터디] 자바 ORM 표준 JPA 프로그래밍 : 엔티티 매핑

by newny 2024. 6. 2.
반응형

Intro


이번 장에서는 객체와 테이블 매핑, 기본 키 매핑, 필드와 컬럼 매핑에 대해 알아보겠다.
 
 
 
 

@Entity


  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 어노테이션을 필수로 붙여야 한다.
  • JPA가 엔티티 객체를 생성할 때 기본생성자를 사용하므로 기본 생성자가 필수(파라미터가 없는 public 또는 protected 생성자)
  • final 클래스, enum, interface, inner 클래스에는 사용할 수 없다.
  • 저장할 필드에 final을 사용하면 안 된다.

 

name 속성

  • JPA에서 사용할 엔티티 이름을 지정한다. 보통 기본값인 클래스 이름을 사용한다.
  • 다른 패키지에 이름이 같은 엔티티가 클래스가 있다면 이름을 지정하여 충돌하지 않도록 해야 한다.
  • 기본값 : 클래스 이름

 
 
 
 

@Table


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

name 속성

  • 매핑할 테이블 이름
  • 기본값 : 엔티티 이름

 

catalog 속성

  • catalog 기능이 있는 데이터베이스에서 catalog를 매핑한다.

 

schema 속성

  • schema 기능이 있는 데이터베이스에서 schema를 매핑한다.

 

uniqueConstraints 속성

  • DDL 생성 시에 유니크 제약조건을 만든다.
  • 2개 이상의 복합 유니크 제약조건도 만들 수 있다. 
  • 이 기능은 스키마 자동생성 기능을 사용해서 DDL을 만들 때만 사용된다.

 
 
 
 

다양한 매핑 사용


@Enumerated

  • 자바의 enum을 사용하여 데이터베이스에 값을 넣고 싶다면 @Enumerated 어노테이션으로 매핑해야 한다.

 
 

@Temporal

  • 자바의 날짜 타입은 @Temporal 어노테이션으로 매핑해야 한다.

 
 

@Lob

  • 문자 속성의 길이 제한이 없어야 할 때 데이터베이스에서는 VARCHAR 타입 대신 CLOB 타입으로 저장해야 한다.
  • @Lob 어노테이션을 사용하면 CLOB, BLOB 타입을 매핑할 수 있다.\

 
 
 
 

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


JPA는 데이터베이스 스키마를 자동을 생성하는 기능을 지원한다. JPA는 매핑 정보와 데이터베이스 방언을 사용해서 데이터베이스 스키마를 생성한다. JPA에서의 DDL-Auto 설정은 persistence.xml에서 설정해야 하지만 나는 현재 Spring Data JPA를 사용 중이기에 application.yml 파일 설정으로 설명하겠다.

jpa:
   hibernate:
      ddl-auto: create

스키마 자동 생성 기능을 사용하면 애플리케이션 실행 시점에 데이터베이스 테이블이 자동으로 생성되므로 개발자가 테이블을 직접 생성하는 수고를 덜 수 있다.
하지만 스키마 자동 생성 기능이 만든 DDL은 운영 환경에서 사용할 만큼 완벽하지는 않으므로 개발환경에서 사용하거나 매핑을 어떻게 해야 하는지 참고하는 정도로만 사용하는 것이 좋다.
 

ddl-auto 속성

  • create : 기존 테이블을 삭제하고 새로 생성한다.
  • create-drop : 애플리케이션이 시작될 때 스키마를 생성하고, 종료될 때 스키마를 삭제한다.
  • update : 기존 데이터베이스 스키마를 업데이트한다. 데이터는 유지되지만, 새로운 엔티티나 변경된 엔티티 필드가 반영된다.
  • validate : 엔티티와 데이터베이스 스키마가 일치하는지 검증한다. 일치하지 않으면 애플리케이션이 실행되지 않는다. 이 설정은 DDL을 수정하지 않는다.
  • none : 아무런 작업도 수행하지 않는다.

 
 
 
 

DDL 생성 기능


@Column

nullable 속성

false로 지정하면 자동생성되는 DDL에 not null 제약조건을 추가할 수 있다.
 

length 속성

자동 생성되는 DDL에 문자의 크기를 지정할 수 있다.
 
 

@Table

UniqueConstraints 속성

해당 어노테이션은 유니크 제약조건을 만들어준다.

@Table(name="MEMBER", uniqueConstraints={@UniqueConstraint(
	name = "NAME_AGE_UNIQUE",
    columnNames = {"NAME", "AGE"})}
ALTER TABLE MEMBER
ADD CONSTRAINT NAME_AGE_UNIQUE UNIQUE (NAME, AGE)

위와 같은 코드를 작성하게 되면 위와 같은 DDL이 생성된다.
 
위와 같은 어노테이션들은 단지 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다. 그래도 이 기능을 사용하면 애플리케이션 개발자가 엔티티만 보고도 손쉽게 다양한 제약 조건을 파악할 수 있는 장점이 있다.
 
 
 
 

기본 키 매핑


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

  • 직접 할당 : 기본 키를 애플리케이션에서 직접 할당한다.
  • 자동 생성 : 대리 키 사용 방식
    • IDENTITY : 기본 키 생성을 데이터베이스에 위임한다.
    • SEQUENCE : 데이터베이스 시퀀스를 사용해서 기본 키를 할당한다.
    • TABLE : 키 생성 테이블을 사용한다.

 
 
 
 

기본 키 직접 할당


@Id 적용 가능 자바 타입

  • 자바 기본형
  • 자바 래퍼(Wrapper) 형
  • String
  • java.util.Date
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger




기본키 자동 생성 


IDENTITY 전략

주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용한다. (MySQL로 예를 들자면 auto_increment)

@GeneratedValue(strategy = GenerationType.IDENTITY)

이 전략을 사용하면 JPA는 기본 키 값을 얻어오기 위해 데이터베이스를 추가로 조회한다.
JDBC3에 추가된 Statement.getGeneratedKeys()를 사용하면 데이터를 저장하면서 동시에 생성된 기본 키 값도 얻어 올 수 있다. 하이버네이트는 이 메서드를 사용해서 데이터베이스와 한 번만 통신한다.

💡 주의
엔티티가 영속 상태가 되려면 식별자가 반드시 필요하다. 그런데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달된다. 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다.

 
 

SEQUENCE 전략

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

@Entity
@SequenceGenerator(
	name = "BOARD_SEQ_GENERATOR",
    sequenceName = "BOARD_SEQ", // 매핑할 데이터베이스 시퀀스 이름
    initialValue = 1, allocationSize = 1)
public class Board {
	
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
    				generator = "BOARD_SEQ_GENERATOR")
    private Long id;
}

initialValue속성은 시퀀스를 처음 사용할 때 사용할 시작 값이다. 데이터베이스 시퀀스의 START WITH 값과는 관계가 있지만, 반드시 일치할 필요는 없다. JPA는 이 값으로 첫 번째 시퀀스 값을 결정한다.
allocationSize의 경우 JPA가 한 번에 미리 할당받을 시퀀스 값이다. allocationSize는 설정하지 않을 경우 기본값이 50이므로 시퀀스 값을 하나씩 할당받고 싶다면 1로 설정해야 한다.
initialValue 속성과 allocationSize 속성은 JPA가 시퀀스를 어떻게 사용할지를 정의하는 설정 값이며, 따라서 데이터베이스의 시퀀스 설정과 직접적으로 일치시킬 필요는 없다.
 
 

TABLE 전략

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

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

 

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

 
 

@TableGenerator 어노테이션 속성

  • name : 식별자 생성기 이름 (필수)
  • table : 키 생성 테이블명 (기본값 : hibernate_sequences)
  • pkColumnName : 시퀀스 컬럼명 (기본값 : sequence_name)
  • valueColumName : 시퀀스 값 컬럼명 (기본값 : next_val)
  • pkColumnValue : 키로 사용할 값 이름 (기본값 : 엔티티 이름)
  • initialValue : 초기 값, 마지막으로 생성된 값이 기준이다. (기본값 : 0)
  • allocationSize : 시퀀스 한 번 호출에 증가하는 수 (기본값 : 50)
  • catalog, schema : 데이터베이스 catalog, schema 이름
  • uniqueContraints : 유니크 제약 조건을 지정할 수 있음
💡 참고
Table 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용한다. 이 전략은 SEQUENCE 전략과 비교해서 데이터베이스와 한 번 더 통신하는 단점이 있다. TABLE 전략을 최적화하려면 @TableGenerator.allocationSize를 사용하면 된다. 이 값을 사용해서 최적화하는 방법은 SEQUENCE 전략과 같다.

 
 
 
 

AUTO 전략


GenerationType.AUTO는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. 예를 들어 오라클을 선택하면 SEQUENCE를, MySQL을 선택하면 IDENTITY를 사용한다.
 
AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다. 특히 키 생성 전략이 아직 확정되지 않은 개발 초기 단계나 프로토타입 개발 시 편리하게 사용할 수 있다.
 
 
 
 

정리


  • 직접 할당 : em.persist()를 호출하기 전에 애플리케이션에서 직접 식별자 값을 할당해야 한다. 만약 식별자 값이 없으면 예외가 발생한다.
  • SEQUENCE : 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.
  • TABLE : 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.
  • IDENTITY : 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다. (IDENTITY 전략은 테이블에 데이터를 저장해야 식별자 값을 획득할 수 있다)

 
 
 
 

참고


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

예를 들어 자연 키인 전화번호를 기본 키로 선택한다면 그 번호가 유일할 수는 있지만, 전화번호가 없을 수도 있고 전화번호가 변경될 수도 있다. 따라서 기본 키로 적당하지 않다. 
 
 

비즈니스 환경은 언젠가 변한다

예를 들어 주민번호를 기본 키로 사용하는 테이블이 있다고 가정하자. 해당 키가 모든 로직에 포함이 되어있는데, 정부 정책이 변경되면서 법적으로 주민등록번호를 저장할 수 없게 되면서 문제가 발생한다. 이로 인해 데이터베이스 테이블은 물론이고 수많은 애플리케이션 로직을 수정해야 한다.
변하지 않을 것 같은 자연 키라도 비즈니스 환경의 변화에 따라 변경해야 할 수도 있게 된다. 따라서 대리키를 기본으로 사용하되 주민등록번호나 이메일처럼 자연 키의 후보가 되는 컬럼들은 필요에 따라 유니크 인덱스를 설정해서 사용하는 것을 권장한다.
 
 

JPA는 모든 엔티티에 일관된 방식으로 대리키 사용을 권장한다

비즈니스 요구사항은 계속해서 변하는데 테이블은 한 번 정의하면 변경하기 어렵다. 그런 면에서 외부 풍파에 쉽게 흔들리지 않는 대리 키가 일반적으로 좋은 선택이라 생각한다. 
 
 
 
 

출처

자바 ORM 표준 JPA 프로그래밍 - 김영한 지음 (에이콘 출판)

반응형

댓글