목차

JPA를 사용하는 데 가장 중요한 일은 엔티티와 테이블을 정확히 매핑하는 것이다.
  • 객체와 테이블 매핑 : @Entity, @Table
  • 기본 키 매핑 : @Id
  • 필드와 컬럼 매핑 : @Column
  • 연관관계 매핑 : @ManyToOne, @JoinColumn

객체와 테이블 매핑

@Entity

  • @Entity가 붙은 클래스는 JPA가 관리, 엔티티라 한다.
  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수이다.
  • 주의
    • 기본 생성자 필수(파라미터가 없는 public 또는 protected 생성자)
    • final 클래스, enum, interface, inner 클래스 --> 사용x
    • 저장할 필드에 final 사용 x

@Table, enumtype

  • @Table은 엔티티와 매핑할 테이블을 지정한다. 생략하면 매핑한 엔티티 이름을 테이블 이름으로 사용한다.
  • createdDate, lastModifiedDate : 자바의 날짜 타입은 @Temporal을 사용해서 매핑한다.
  • roleType : 자바의 enum을 사용하려면 @Enumerated 어노테이션으로 매핑해야한다.
  • 회원을 설명하는 필드는 길이 제한이 없다. 따라서 데이터 베이스의 VARCHAR 타입 대신 CLOB 타입으로 저장해야 한다. @Lob을 사용하면 CLOB, BLOB 타입을 매핑할 수 있다.
package jpabook.start; 
import javax.persistence.*; 
import java.util.Date; 

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

	//== 추가 == 
	@Enumerated (EnumType. STRING) 
	private RoleType roleType; // 1

	@Temporal (TemporalType. TIMESTAMP) 
	private Date createdDate; // 2

	@Temporal (TemporalType. TIMESTAMP)  
	private Date lastModifiedDate; // 2
 
	@Lob 
	private String description; //3
}

//Getter, Setter 
package jpabook.start; 

public enum RoleType { 
	ADMIN, USER
}

DDL 생성 기능

  • 제약조건 추가 : 회원 이름 필수, 10자 초과 X
@Column(nullable = false, length =10)
  • 유니크 제약조건 추가
@Table(uniqeConstraints = (@UniqueConstraint(name = "NAME_AGE_UNIQUE", columnNames ={"NAME", "AGE"})))
  • DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

@Column

옵션
설명
name 필드와 매핑할 테이블의 컬럼 이름
insertable, updatable 등록, 변경 가능 여부 (기본 값 : TRUE)
nullable(DDL) null 가능 여부, false 설정하면 DDL 생성 시에 Not Null 조건이 붙다.
unique(DDL) @Table의 uniqueConstraints와 같지만 한 컬럼에 간단한 유니크 제약조건을 걸 때 사용한다.
columnDefinition(DDL) 데이터베이스 컬럼 정보를 직접 줄 수 있다.
ex) varchar(100) default 'EMPTY'
length(DDL) 문자 길이 제약 조건, String 타입에만 사용 (기본 값 : 255)
precision, scale(DDL) 소수의 자릿수 설정 (기본 값 : precision=19)

@Enumerated

  • 자바 enum 타입을 매핑 할 때 사용
  • 주의 ORDINAL 사용 X
    • EnumType.ORDINAL : enum 순서를 데이터 베이스에 저장
      • enum의 순서대로 0,1,2,3... 저장 된다.(Integer값 저장)
        • 따라서 엄청난 혼란을 줄 수 있다. 예를 들면 ADMIN이 0번 이었는데, GUEST가 0번으로 바뀌면 이전에 저장 되어있던 0번 값(=ADMIN)과 새로 저장된 값 GUEST(=0번 값)이 헷갈리게 된다.
    • EnumType.STRING : enum 이름을 데이터베이스에 저장 (*항상 이렇게 사용)
      • enum 설정 값 이름 그대로를 저장한다.

@Temporal

날짜 타입(java.util.Date, java.util.Calendar) 매핑할 때 사용한다.

  • 참고 : LocalDate, LocalDateTime을 사용할 때는 생략 가능(최신 하이버네이트 지원)
  • TemporalType.DATE : 날짜, 데이터베이스, date 타입과 매핑 (예 : 2013-10-11)
  • TemporalType.TIME : 시간, 데이터베이스, time 타입과 매핑 (예 : 11:11:11)
  • TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스, timestamp 타입과 매핑(예 : 2013-10-11 11:11:11)

아래와 같이 적으면 어노테이션이 필요없다!

private LocalDate localDate;
private LocalDateTime localDateTime;

@Lob

데이터베이스 BLOB, CLOB 타입과 매핑

@Lob에는 지정할 수 있는 속성이 없고, 대신에 매핑하는 필드 타입이 문자면, CLOB으로 매핑하고 나머지는 BLOB로 매핑한다.

@Transient

객체 임시로 어떤 값을 넣고 싶을 때 사용하고 데이터베이스에는 반영이 안된다.

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

사실상 운영에선 사용하진 않고, 개인적으로 개발할때 정도 사용한다.
  • DDL을 애플리케이션 실행 시점에 자동 생성
  • 테이블 중심 -> 객체 중심
  • 데이터베이스 방언을 활용해서 데이터베이스에 맞는 적절한 DDL 생성

xml 다음과 같이 입력하면 초기 실행 시 자동으로 테이블을 생성한다.

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

위의 value 속성은 개발 단계마다 다르게 생성할 수있다.

  • 개발 초기 단계에는 create 또는 update
  • 테스트 서버는 update 또는 validate(엔티티와 테이블이 정상 매핑되었는지만 확인)
  • 스테이징과 운영서버는 validate 또는 none
  • 운영 장비에는 절대 create, create-drop, update 사용하면 안된다.

기본 키 매핑

기본 키 매핑 어노테이션

  • @Id
  • @GeneratedValue
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

기본 키 매핑 방법

데이터베이스마다 기본 키를 생성하는 방식이 서로 다르므로 이 문제를 해결하기는 쉽지 않다. JPA는 이런 문제들을 어떻게 해결하는지 알아보자
  • 직접 할당 : @Id만 사용
  • 자동 생성(@GeneratedValue)
    • IDENTITY : 데이터베이스에 위임(DB에 따라서 알아서(임의로) 해줌), MYSQL, PostgreSQL, SQL Server, DB2에서 사용
      • MySQL의 AUTO_INCREMENT
    • SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, ORACLE
      • @SequenceGenerator 필요
    • TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용
      • @TableGenerator 필요

기본 키 직접 할당 전략

@Id 적용 가능 자바 타입

  • 자바 기본형
  • 자바 래퍼wrapper형
  • String
  • java.util.Date
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger
SEQUENCE 전략

sequenceName 속성의 이름으로 BOARD_SEQ 를 지정했는데 JPA는 이 시퀸스 생성기를 실제 데이터베이스 BOARD_SEQ 시퀀스와 매핑한다. sequenceName을 따로 설정하지 않으면 hibernate_sequence와 같이 자동으로 설정된다.

@Entity
@SequenceGenerator(
	name = "BOARD_SEQ_GENERATOR".
	sequenceName = ”BOARD_SEQ”, //매핑할 데이터베이스 시퀀스 이름
	initialvalue = 1, 
	allocationsize = 1)
public class Board {

	@IdQGeneratedValue(
	strategy = GenerationType.SEQUENCE,
	generator = "BOARD_SEQ_GENERATOR")
	private Long id;
	...
}

call next value for MEMBER_SEQ란?
영속성 컨텍스트에 의해 JPA는 항상 PK값을 알아야한다. 그래서 MEMBER_SEQ값에서 id값을 조회한다. 그러고 나서 em.persist를 해준다.

commit 하는 시점에 insert쿼리가 날라간다.
SquenceGenerator.allocationSize의 기본값이 50인 이유는 최적화 때문이다 allocationSize 값이 50이면 시퀀스를 한 번에 50 증가 시킨 다음에 1~50까지는 메모리에서 식별자를 할당한다. 이 최적화 방법은 시퀀스 값을 선점 하므로 여러 JVM이 동시에 동작 해도 기본 키 값이 충돌하지 않는 장점이 있다. 반면에 데이터베이스에 직접 접근해서 데이터를 등록할 때 시퀀스 값이 한번에 많이 증가한다는 점을 염두해 두어야 한다. 참고로 앞서 설명한 hibernate.id.new_generator_mappings 속성을 true로 설정해야 지금까지 설명한 최적화 방법이 적용된다.

TABLE 전략(운영에서 잘 쓰이지 않음)

TABLE 전략은 키 생성 전용 테이블을 하나 만들고 여기에 이름과 값으로 사용할 컬럼을 만들어 데이터베이스 시퀀스를 흉내내는 전략이다.
  • 장점 : 모든 데이터베이스에 적용 가능
  • 단점 : 성능
@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;
	...
}
TABLE 전략과 최적화 TABLE 전략은 값을 조회하면서 SELECT 쿼리를 사용하고 다음 값으로 증가시키기 위해 UPDATE 쿼리를 사용한다. 이 전략은 SEQUENCE 전략과 비교해서 데이터베이스와 한번 더 통신하는 단점이 있다. TABLE 전략을 최적화하려면 TableGenerator.allocationSize를 사용하면 된다.

권장하는 식별자 전략

  • 기본 키 제약 조건 : null 아님, 변하면 안된다.
  • 미래까지 이 조건에 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
  • 예를 들어 주민등록번호도 기본 키로 적절하지 않다.
  • 권장 : Long형 + 대체키 + 키 생성전략 사용

AUTO 전략

GenerationType.AUTO는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다. AUTO 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것이다. AUTO를 사용할 때 SEQUENCETABLE 전략이 선택되면 시퀀스나 키 생성용 테이블을 미리 만들어 두어야 한다.

IDENTITY 전략

기본 키 생성을 데이터베이스에 위임

IDENTITY 전략은 지금 설명한 AUTO INCREMENT를 사용한 예제처럼 데이터베이스에 값을 저장하고 나서야 기본 키 값을 구할 수 있을 때 사용한다.

private static void logic (EntityManager em) { 
	Board board = new Board(); em.persist (board); 
	System.out.println("board.id = " + board.getId()); 
}
//출력: board.id = 1
문제점 : IDENTITY 전략은 데이터를 데이터베이스에 INSERT한 후에 기본 키 값을 조회할 수 있다.
티티가 영속 상태가 되려면 식별자가 반드시 필요하다. 그런데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL 이 데이터베이스에 전달된다. 따라서 이 전략은 트랜잭션을 지원하는 쓰기 지연이 동작하지 않는다. 왜냐하면 JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행합니다. 그리고 AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있습니다.

'Back-end > JPA' 카테고리의 다른 글

객체지향 쿼리 언어1 - 기본 문법  (0) 2022.09.24
연관관계 매핑 기초  (0) 2022.08.22
[JPA] JPA 영속성 컨텍스트  (0) 2022.05.05
[JPA] Batch Insert  (0) 2022.05.05
JPA 소개  (0) 2022.03.14

+ Recent posts