1. 엔티티 매핑: 기본 개념

xellos·2022년 6월 19일
0

JPA

목록 보기
4/7

JPA를 사용하는데 가장 중요항 일은 엔티티와 테이블을 정확히 매핑하는 것이다. 따라서 매핑 애노테이션을 숙지하고 사용해야 한다. JPA는 다양한 매핑 애노테이션을 지원하는데 크게 4가지로 분류할 수 있다.

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

필드와 컬럼을 매핑하는 애노테이션은 기능을 하나씩 설명하기에는 내용이 많으므로 필요할 때 찾아볼 수 있도록 정리하였다.

@Entity
@Table(name="MEMBER")
public class Member {
	
    @Id @Column(name="ID)
	private String id;
    
    @Column(name="NAME")
   	private String username;
    
    private Integer age;
    
    ...
}

@Entity

해당 애노테이션이 붙은 클래스는 JPA가 관리한다. 기본값을 설정하지 않으면 클래스 이름을 그대로 사용한다.

  • 주의사항
  1. 기본 생성자는 필수다. 생성자를 임의로 하나 생성하면 기본 생성자를 반드시 추가해야 한다.
  2. final, enum, interface, inner 클래스에는 사용할 수 없다.
  3. 저장할 필드에 final 키워드를 사용할 수 없다.

@Table

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

DDL 생성 기능

회원 엔티티에서 이름은 필수로 입력되어야 하고, 10자를 초과하면 안된다는 제약 조건이 추가되었다고 가정하자. 이때, 스키마 자동 생성하기를 통해 만들어지는 DDL에 이 제약 조건을 추가해보자.

  • nullable = false
  • maxLength = 10
@Entity
@Table(name="MEMBER")
public class Member {
	
    @Id @Column(name="id")
    private String id;
    
    @Column(name="NAME", nullable=false, length=10)
    private String username;
    
    ...
}

이번에는 유니크 제약 조건을 만들어주는 @TableuniqueConstrains 속성을 살펴보자.

@Entity
@Table(name="MEMBER", uniqueConstraints={
	@UniqueConstraint(
    	name="NAME_AGE_UNIQUE",
        columnNames={"NAME", "AGE"})
})
public class Member {
	
    @Id @Column(name="ID")
    private String id;
    
    @Column(name="NAME", nullable=false, length=10)
    private String username;
    
    ...
}

기본 키매핑

JPA가 제공하는 DB 기본 키 생성 전략은 다음과 같다.

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

자동 생성 전략이 다양한 이유는 DB벤더마다 지원하는 방식이 다르기 때문이다. 예를 들면 오라클은 시퀀스를 제공하지만 MySQL은 제공하지 않는다. 대신 기본 키값을 자동으로 채워주는 AUTO_INCREMENT 기능을 제공한다. 따라서 각 키 생성 전략들을 사용하는 DB에 의존한다. TABLE 전략은 키 생성용 테이블을 하나 만들어두고 마치 시퀀스처럼 사용하는 방식이다. 이 전략은 모든 DB에서 사용할 수 있다.

기본키를 직접 할당하려면 @Id 만 사용하면 되고 자동 생성 전략을 사용하려면 @GeneratedValue를 추가하고 원하는 키생성 전략을 사용하면 된다. 선택하지 않으면 DB 종류에 따라 자동으로 생성한다.

1) 기본 키 직접 할당 전략

@Id 적용이 가능한 자바 타입은 다음과 같다.

  • 자바 기본형
  • 자바 래퍼형
  • String
  • Date
  • BigDecimal
  • BigInteger

2) IDENTITY 전략

기본 키 생성을 DB에 위임하는 전략이다. 주로 MySQL, PostgresQL, SQL Server 등에서 사용된다. IDENTITY 전략은 DB에 값을 저장하고 나서야 기본 키값을 구할 수 있을 때 사용한다.
IDENTITY 전략을 사용하려면 아래와 같이 속성을 지정하면 된다.

@GeneratedValue(strategy=GenerationType.IDENTITY)
  • 최적화
    위에서 언급했던 것처럼 이 전략은 DB에 값을 저장할 후에 기본 키값을 조회할 수 있다. 따라서 기본에는 추가로 DB를 조회해야 했다. 그러나 JDBC3에 추가된 Statement.getGeneratedKeys() 를 사용하면 데이터를 저장하면서 동시에 기본 키값도 얻어올 수 있다. → 하이버네이트가 사용
  • 주의
    이 전략은 엔티티를 DB에 저장해야 식별자를 구할 수 있으므로 em.persist() 호출시 즉시 SQL이 DB에 전달된다. → 쓰기 지연 동작이 발생하지 않는다.

3) SEQUENCE 전략

DB 시퀀스는 유일한 값을 순서대로 생성하는 특별한 DB 오브젝트다. 이 전략은 이 시퀀스를 사용하여 기본키를 생성한다. 오라클, PostgresQL, H2 등에서 사용할 수 있다.

  • 시퀀스 DDL
CREATE TABLE board (
	id BIGINT NOT NULL PRIMARY KEY,
    data VARCHAR(255)
)

//시퀀스 생성: 시퀀스명 -> BOARD_SEQ
  • 시퀀스 매핑 코드
@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;
    
    ...
}

시퀀스 사용 코드는 IDENTITY 전략과 같지만 내부 동작 방식은 완전히 다르다. 시퀀스 방식은 em.persist() 를 호출할 때 먼저 DB 시퀀스를 사용해서 식별자를 조회한다. 그리고 조회한 식별자를 엔티티에 할당한 후에 이를 영속성 컨텍스트에 저장한다. 이후 트랜잭션을 커밋해서 플러시가 일어나면 DB에 저장한다.


4) AUTO 전략 - default

위에서 본 것처럼 DB는 종류도 많고 기본 키를 만드는 방법도 다양하다. GenerationType.AUTO는 선택한 DB 방언에 따라 위의 전략 중 하나를 자동으로 선택한다. 이 전략의 장점은 DB를 변경해도 코드를 수정할 필요가 없다는 것이다. 특히 키생성 전략이 아직 확정되지 않은 개발 초기 단계나 프로토타입 개발시 편리하다.

5) 정리

  • 직접 할당: em.persist()를 호출하기 전에 애플리케이션에서 직접 실별자 값을 할당해야 한다.
  • SEQUENCE: DB 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.
  • TABLE: DB 시퀀스용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.
  • IDENTITY: DB에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.

필드와 컬럼 매핑

1) @Column

객체 필드를 테이블 컬럼에 매핑한다. 가장 많이 사용되고 기능도 많다. 속성중에 주로 name, nullable이 주로 사용되고 나머지는 잘 사용되지 않는 편이다.

@Column 생략

@Column 생략시 대부분 속성의 기본값이 적용되는데, 자바 기본 타입일 때는 nullable 속성에 예외가 있다.

int age // null 불가능

위에서 보듯 자바 기본 타입은 null 값을 입력할 수 없다. Integer와 같이 객체 타입일 때만 null이 허용된다. 따라서 자바 기본타입으로 테이블 생성시 not null 제약 조건을 추가하는 것이 안전하다.

JPA는 이를 고려하여 자동으로 기본타입에는 not null 제약 조건을 추가한다. 반면 Integer와 같은 래퍼타입이면 설정하지 않는다.

그런데 @Column 을 사용하면 기본값이 nullable=true 이므로 기본 자료형에는 nullable=false로 설정하는 것이 안전하다.


2) @Enumerated

Enum 타입 정의

enum RoleType {
	ADMIN, USER
}

엔티티 매핑 사용 예시

@Enumerated(EnumType.STRING)
private RoleType roleType;

3) @Temporal

날짜 타입을 매핑할 때 사용한다. 생략시 timestamp 로 정의된다.

@Temporal(TemporalType.DATE)
private Date date;

@Temporal(TemporalType.TIME)
private Date time;

@Temporal(TemporalType.TIMESTAMP)
private Date timestamp;

4) @Lob

DB의 BLOB, CLOB 타입과 매핑한다. 여기에는 지정할 수 있는 속성이 없다. 대신에 매핑하는 필드 타입이 문자면 CLOB, 나머지는 BLOB로 매핑한다.

@Lob
private String lobString;

@Lob
private byte[] lobByte;

5) Transient

해당 애노테이션이 적용되는 필드는 매핑되지 않는다. 따라서 DB에 저장하지 않고 조회하지도 않는다. 객체에 임시로 어떤 값을 보관하고 싶을 때 사용한다.

0개의 댓글