자바 ORM 표준 JPA 프로그래밍 - 기본편 챕터 4 정리

정종일·2023년 5월 22일
0

Spring

목록 보기
9/18

엔티티 매핑

JPA는 다양한 매핑 어노테이션들을 제공한다

  1. 객체와 테이블 매핑 : @Entity, @Table
  2. 기본 키 매핑 : @Id
  3. 필드와 컬럼 매핑 : @Column
  4. 연관관계 매핑 : @ManyToOne, @JoinColumn

1. @Entity


해당 어노테이션이 붙은 클래스는 JPA가 관리하는것이다

⚠️ 주의사항

  • 기본 생성자는 필수
  • final 클래스, enum, interface, inner 클래스에는 사용 불가!
  • 저장할 필드에 final을 사용하면 안 된다.
  • 자바는 생성자가 하나도 없으면 기본 생성자를 자동으로 만든다. 하지만 생성자를 하나이상 직접 만들면 자바는 기본 생성자를 자동으로 만들지 않으므로 기본 생성자를 직접 만들어야 한다.

2. @Table


엔티티와 매핑할 테이블을 지정한다

name: 매핑할 테이블 이름
catalog: catalog 기능이 있는 데이터베이스에서 catalog 매핑
schema: schema 기능이 있는 DB에서 schema 매핑
uniqueConstraints(DDL): DDL 생성시에 유니크 제약조건을 만듬.
2개이상의 복합 유니크 제약조건 생성가능
스키마 자동 생성기능을 사용해서 DDL을 만들때만 사용

3. 다양한 매핑 사용


  1. @Enumerated
    • enum을 사용하려면 해당 어노테이션 사용
  2. @Temporal
    • 자바의 날짜 타입은 해당 어노테이션 사용
  3. @Lob
    • DB에서 CLOB, BLOB 타입을 매핑할 때 사용
    • 필드의 길이제한을 없도록 해줄 때 사용

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


JPA는 매핑정보와 데이터베이스 방언을 사용하여 데이터베이스 스키마를 생성한다.

// persistence.xml 에 추가

<property name="hibernate.hbm2ddl.auto" value="create"/>
// 스키마 자동 생성

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

hibernate.show_sql 속성을 true로 설정하면 콘솔에 실행되는 테이블 생성 DDL을 출력할 수 있다

hibernate.hbm2ddl.auto 속성

create: # create
create-drop: create 속성에 추가로 어플리케이션을 종료할 때 생성한 DDL을 제거
(DROP + CREATE + DROP)
update: 데이터베이스 테이블과 엔티티 매핑정보를 비교해서 변경 사항만 수정
validate: 데이터베이스 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 경고를 남기고 어플리케이션을 실행하지 않는다. DDL수정X
none: 자동 생성기능을 사용하지 않는다

⚠️ 주의사항

  1. HBM2DDL
    • 운영 서버에서 create-drop, create, update처럼 DDL을 수정하는 옵션 절대 사용 금지!
    • 개발환경에 따른 추천 전략
      1. 개발초기 → create || update
      2. 초기화 상태로 자동화된 테스트 진행 → create || create-drop
      3. 테스트 서버 → update || validate
      4. 스테이징과 운영 → validate || none

👍 참고사항

  1. JPA는 2.1부터 스키마 자동 생성 기능을 표준으로 지원
    • 하지만 하이버네이트의 update, validate 옵션은 미지원
  2. 이름 매핑전략 변경
    • 자바는 카멜표기법을 주로 사용하지만 DB는 관례상 언더스코어를 사용! ex) 자바 : roleType , DB : role_type
    • 하이버네이트는 org.hibernate.cfg.ImprovedNamingStrategy 클래스를 제공, 테이블 명이나 컬럼 명이 생략되면 카멜 표기법을 테이블의 언더스코어 표기법으로 자동 매핑!
    • 직접 매핑해주고싶다면 @Column(name="role_type") 과 같이 매핑해주면 된다

5. DDL 생성 기능


@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;
}
  • nullable 속성 값이 false 이면 자동생성되는 DDL에 not null 조건 추가 가능
  • length 속성 값을 사용하면 DDL에 문자의 크기를 지정 가능
  • @uniqueConstraints 를 통해 unique 조건을 걸 수 있다

위 기능들은 DDL을 자동으로 생성할 때에만 사용된다 !!

DDL을 직접 만든다면 사용할 이유가 없지만 개발자가 엔티티만 보고도
손쉽게 제약조건을 파악할 수 있다는 장점이 존재한다.

6. 기본 키 매핑


기본 키 생성 전략은 두가지가 있다

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

데이터베이스 벤더마다 지원하는 방식이 다르기 때문에 자동생성 전략 또한 다양하다 !!

기본 키를 직접 할당하려면 @Id 만 사용하고, 자동 생성 전략을 사용하려면 @GeneratedValue를 추가하고 원하는 키 생성 전략을 선택하면 된다 !

⚠️ 주의사항

키 생성 전략을 사용하려면 persistence.xmlhibernate.id.new_generator_mapping = true 속성을 반드시 추가해야한다

이 옵션을 true 로 설정하면 키 생성 성능을 최적화하는 allocationSize 속성을 사용하는 방식이 달라진다 (뒤에서 설명)

기본 키 직접 할당 전략

@Id
@Column(name = "id")
private String id;

적용 가능 타입은 아래와 같다

  • 자바 기본형
  • 자바 래퍼형
  • String
  • java.util.Date
  • java.sql.Date
  • java.math.BigDecimal
  • java.math.BigInteger
Board board = new Board();
board.setId("id");
em.persist(board);

em.persist() 로 엔티티를 저장하기 전에 어플리케이션에서 기본 키를 직접 할당하는 방법

👍 참고사항

기본 키 직접 할당 전략에서 식별자 값 없이 저장하면 예외가 발생. 하이버네이트를 구현체로 사용하면 JPA 최상위 예외인 javax.persistence.PersistenceException 이 발생, 내부에 하이버네이트 예외인 org.hibernate.id.identifierGenerationException 예외를 포함하고 있다.

IDENTITY 전략

기본 키 생성을 데이터베이스에 위임하는 전략

주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용

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

이 전략을 사용하면 JPA는 기본 키 값을 얻어오기 위해 데이터베이스를 추가로 조회한다.

private static void logic(EntityManager em) {
		Board board = new Board();
		em.persist(board);
		System.out.println("board.id = " + board.getId());
}
// board.id = 1

위 코드를 보면 em.persist() 를 호출해서 엔티티를 저장한 직후에 할당된 식별자 값을 출력

출력된 값 1은 저장 시점에 데이터베이스가 생성한 값을 JPA가 조회한 것

👍 참고사항

IDENTITY 전략은 데이터를 데이터베이스에 INSERT 한 후에 기본 키 값을 조회할 수 있다.

즉, 엔티티에 식별자 값을 할당하려면 JPA는 추가로 데이터베이스를 조회해야한다 !

but, JDBC3에 추가된 Statement.getGeneratedKeys()를 사용하면 데이터를 저장함과 동시에 생성된 기본 키 값도 얻어올 수 있어 한번만 통신하면 된다 !

⚠️ 주의사항

엔티티가 영속상태가 되려면 식별자가 필요!

하지만 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스에 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달된다. 따라서 이 전략은 트랜잭션을 지원하는 쓰기전략에서 동작하지 않는다 !

SEQUENCE 전략

시퀀스를 사용해서 기본 키를 생성

주로 Oracle, 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;
  1. @SequenceGenerator를 사용하여 BOARD_SEQ_GENERATOR 라는 시퀀스 생성기 등록
  2. sequenceName 속성의 이름으로 BOARD_SEQ 를 지정, JPA는 실제 데이터베이스의 BOARD_SEQ 와 매핑
  3. GenerationType.SEQUENCE로 설정, generator = "BOARD_SEQ_GENERATOR" 로 시퀀스 생성기 선택
  4. id 식별자 값은 BOARD_SEQ_GENERATOR 시퀀스 생성기가 할당

IDENTITY 전략과 같지만 내부 동작방식은 다르다.

간단하게 설명하면 IDENTITY
데이터베이스에 저장 → 식별자 할당 → 식별자 조회 순이고

SEQUENCE 같은 경우
데이터베이스 시퀀스를 사용하여 식별자 조회 → 식별자 할당트랜잭션 커밋 & 플러시 → 데이터베이스에 저장한다.

@SequenceGenerator

name: 식별자 생성기 이름
seqeunceName: 데이터베이스에 등록되어있는 시퀀스 이름
initialValue: DDL 생성시에만 사용
시퀀스 DDL을 생성할 때 처음 시작하는 수 지정
allocationSize: 시퀀스 한 번 호출에 증가하는 수
(성능 최적화에 사용)
catalog, schema: 데이터베이스 catalog, schema 이름

// 매핑할 DDL
create sequence [sequecneName]
start with [initialValue] increment by [allocationSize]

⚠️ 주의사항

SequenceGenerator.allocationSize의 기본 값이 50인 것에 주의해야 한다. JPA가 기본으로 생성하는 데이터베이스 시퀀스는 최적화의 이유로 호출마다 값이 50씩 증가한다. 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야 한다 !!!

SEQUENCE 전략과 최적화

위 글을 참고하자 ..!

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;

@TableGenerator 를 사용하여 테이블 키 생성기를 등록, @GeneratedValue.generator에 키 생성기 지정. Table 전략은 Sequence 대신에 테이블을 사용한다는 것만 제외하면 Sequence 전략과 내부 동작 방식이 같다

@TableGenerator

name: 식별자 생성기 이름
table: 키 생성 테이블명
pkColumnName: 시퀀스 컬럼명
valueColumnName: 시퀀스 값 컬럼명
pkColumnValue: 키로 사용할 값 이름
initialValue: 초기 값, 마지막으로 생성된 값이 기준
allocationSize: 시퀀스 한 번 호출에 증가하는 수
(성능 최적화에 사용)
catalog, schema: 데이터베이스 catalog, schema 이름
uniqueConstraints(DDL): 유니크 제약 조건 지정

👍 참고사항

Table 전략은 Sequence 전략과 비교해서 데이터베이스와 한번 더 통신하는 단점 존재.

이를 최적화 하려면 allocationSize를 사용하면 되며, Sequence 전략과 방법이 같다.

AUTO 전략

GenerationType.AUTO 는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다.

@Entity
public class Board {
		@Id
		@GeneratedValue(strategy = GenerationType.AUTO)
		private Long id;
}

Auto 전략의 장점은 데이터베이스를 변경해도 코드를 수정할 필요가 없다는 것!

기본 키 매핑 정리

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

👍 참고사항

데이터베이스 기본 키는 3가지 조건을 만족해야함

  1. Not null
  2. Unique
  3. Not change

테이블의 기본 키를 선택하는 전략

  1. 자연 키
    • 비지니스에 의미가 있는 키
    • ex) 주민번호, 이메일, 전화번호 등
  2. 대리 키
    • 임의로 만들어진 키

비지니스 환경은 언젠가 변하기 때문에 자연키보다는 대리키를 사용하자 !!

ex) 주민번호 자연키를 사용하다가 개인정보 보호법 등의 법적 문제가 생기면 키로 이용하지 못한다. 엄청난 양의 수정이 필요하게 됨

기본적인 원칙으로 기본 키는 변하면 안 된다 !!

7. 필드와 컬럼 매핑


필드와 컬럼 매핑 분류

@Column: 컬럼 매핑
@Enumerated: 자바의 Enum 타입 매핑
@Temporal: 날짜 타입 매핑
@Lob: BLOB, CLOB 타입 매핑
@Transient: 특정 필드를 데이터베이스에 매핑하지 않음
@Access: JPA가 엔티티에 접근하는 방식 지정

@Column

객체 필드를 테이블 컬럼에 매핑

name: 필드와 매핑할 테이블의 컬럼 이름
insertable: 엔티티 저장 시 이 필드도 같이 저장
updatable: 엔티티 수정 시 이 필드도 같이 수정
table: 하나의 엔티티를 두개 이상의 테이블에 매핑할 때 사용
nullable: null 값 허용 여부 설정
unique(DDL): 한 컬럼에 간단히 유니크 제약조건을 걸 때 사용
2개 이상의 유니크 제약조건은 @Table.uniqueConstraints를 사용해야한다
columnDefinition(DDL): 데이터베이스 컬럼 정보를 직접 줄 수 있음
length(DDL): 문자 길이 제약조건, String type에만 사용
precision, scale(DDL): BigDecimal 타입에서 사용

👍 참고사항

@Column을 생략하게 되면 아래와 같은 차이가 있다.

int data;
→ data integer not null

Integer data;
→ data integer

@Column
int data
→ data integer

@Enumerated

value: EnumType.ORDINAL : enum 순서를 데이터베이스에 저장 (0부터 시작)
EnumType.STRING : enum 이름을 데이터베이스에 저장

⚠️ 주의사항

ORDINAL은 데이터베이스에 저장되는 데이터 크기가 작지만 enum이 중간에 추가되면 데이터베이스에 기존에 저장되어있던 값은 따로 수정을 해주어야 한다..!!

@Temporal

value: TemporalType.DATE (2013-10-11)
TemporalType.TIME (11:11:11)
TemporalType.TIMESTAMP (2013-10-11 11:11:11)

datetime : MySQL

timestamp : Oracle, H2, PostgreSQL

@Lob

지정할 수 있는 속성이 없다.

매핑하는 필드 타입이 문자면 CLOB, 나머지는 BLOB으로 매핑

@Transient

이 필 드는 매핑하지 않는다. 따라서 데이터베이스에 저장하지 않고 조회도 하지 않는다.
객체에 임시로 어떤 값을 보관하고 싶을 때 사용

@Access

JPA가 엔티티 데이터에 접근하는 방식 지정

  • 필드 접근 : AccessType.FIELD, private 이어도 접근 가능
  • 프로퍼티 접근 : AccessType.PROPERTY 접근자(Getter) 를 사용한다.
profile
제어할 수 없는 것에 의지하지 말자

0개의 댓글