개발 일기

[섹션4] 엔티티 매핑 본문

Back-End/자바 ORM 표준 JPA 프로그래밍

[섹션4] 엔티티 매핑

개발 일기장 주인 2024. 3. 25. 22:26
엔티티 매핑은 크게 4가지로 볼 수 있다.
1. 객체와 테이블 매핑 (@Entity, @Table)
2. 필드와 컬럼 매핑 (@Column)
3. 기본 키 매핑 (@Id)
4. 연관관계 매핑 (@ManyToOne, @JoinColumn)
이중 1,2,3을 이번 섹션에서 다루고 4번을 다음 섹션에서 다룰 것이다.

객체와 테이블 매핑 (@Entity, @Table)

@Entity

해당 어노테이션이 붙은 클래스는 JPA가 관리하며 그것을 엔티티라고 한다.

JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수

주의
- 기본 생성자 필수(파라미터가 없는 public 또는 protected 생성자)
- final 클래스, enum, interface, inner 클래스 사용 X
- 저장할 필드에 final 사용 X

 

  • name 속성
    : JPA에서 사용할 엔티티 이름을 지정해주는 속성으로 Default값은 클래스 이름을 그대로 사용하지만 다른 패키지에 같은 클래스명이 있는 경우에 문제가 발생할 수 있기 때문에 따로 지정해줘야한다. 그러나 가급적으로 기본값을 사용해야한다.

@Table

해당 어노테이션은 지정해준 엔티티와 매핑해줄 테이블을 지정해주는 역할을 한다.

속성 기능 기본값
name
매핑할 테이블 이름
엔티티 이름을 사용
catalog 데이터베이스 catalog 매핑 -
schema 데이터베이스 schema 매핑 -
uniqueConstraints (DDL) DDL 생성 시에 유니크 제약 조건 생성 -

 

❖ DDL은 데이터베이스 정의 언어(Database Definition Language)의 약자로 데이터베이스의 구조를 정의하는데 사용된다.

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

DDL을 애플리케이션 실행 시점에 자동 생성하고 설정해준 데이터베이스 방언을 활용하여 지정해준 데이터베이스에 맞는 적절한 DDL 생성한다. 그러나 이렇게 생성된 DDL은 개발 장비에서만 사용하고 실제 운영 서버에서는 사용해서는 안된다.

운영 장비에는 절대 create, create-drop, update 사용하면 안된다.
• 개발 초기 단계는 create 또는 update
• 테스트 서버는 update 또는 validate
• 스테이징과 운영 서버는 validate 또는 none

그러나 이때 update는 ALTER로 필드 추가나 변경만 되지 필드삭제는 안됨.

 

DDL 생성 기능

위에서 @Table, @Entity의 속성들을 건드리는 것은 실제 런타임(JPA의 실행 로직)에 영향을 주지만 @Column(unique = true, length = 10) 이런식으로 제약조건을 걸어주는 것은 그냥 DDL 생성에 영향을 끼치는 것이지 런타임에 영향을 끼치는 것은 아니다. 그런 것을 DDL 생성 기능이라고 한다.

필드와 컬럼 매핑 (@Column)

@Column

: 컬럼 매핑

속성
 설명
기본값
name
필드와 매핑할 테이블의 컬럼 이름
객체의 필드 이름
insertable,
updatable
등록, 변경 가능 여부
TRUE
nullable(DDL)
null 값의 허용 여부를 설정한다.
false
로 설정하면 DDL 생성 시에 not null 제약조건이 붙는다.
 
unique(DDL)
@TableuniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제 약조건을 걸 때 사용한다. but 이름 설정이 힘들어 @Table의 uniqueConsraints 속성을 통해 걸자.
 
columnDefinition
(DDL)
데이터베이스 컬럼 정보를 직접 줄 수 있다.
ex) varchar(100) default ‘EMPTY'
필드의 자바 타입과
방언 정보를 사용해
length(DDL)
문자 길이 제약조건, String 타입에만 사용한다.
 255
precision,
scale(DDL)
BigDecimal 타입에서 사용한다(BigInteger도 사용할 수 있다). precision은 소수점을 포함한 전체 자 릿수를, scale은 소수의 자릿수 다. 참고로 double, float 타입에는 적용되지 않는다. 아주 큰 숫자나 정 밀한 소수를 다루어야 할 때만 사용한다.
precision=19, scale=2

@Enumerated

: 자바의 enum 타입을 매핑할 때 사용한다. 

EnumType.ORDINAL(enum 순서를 DB에 저장)과 EnumType.STRING(enum 이름을 DB에 저장)이 있다. 

ex) RoleType이라는 enum에 USER, ADMIN이 있어서 ORDINAL이라면 RoleType.USER은 0, RoleType.ADMIN은 1로 저장이되고 STRING의 경우 USER,ADMIN이 그대로 저장된다.

주의! ORDINAL 사용X

왜냐하면 요구사항이 추가되어 GUEST가 USER 앞에 추가된 경우 기존에 USER라고 추가된 것은 0, ADMIN으로 저장된 것은 1로 이미 저장되어 있기때문에 0이 GUEST, 1이 USER로 잘못 매핑되기 때문에 문제가 된다.


@Temporal

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

그러나 최신 하이버네이트는 LocalDate, LocalDateTime을 지원해주기 때문에 @Temporal을 생략 가능하다.

// @Temporal 없이 아래와 같이만 해줘도 처리됨
private LocalDate time1;
private LocalDateTime time2;

 

@Lob

: 데이터베이스 BLOB, CLOB 타입과 매핑한다.

이때 따로 개발자가 지정해줄 수 있는 속성은 없고

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

 

@Transient

: 특정 필드를 컬럼에 매핑하지 않음(매핑 무시)

즉, 필드를 매핑하지 않고 데이터베이스에 저장 및 조회를 하지 않는다.

 

기본 키 매핑 (@Id)

직접 id 할당 시에 @Id만 달아주지만 자동 생성 시 @GeneratedValue를 달아주어야 한다.

 

@GeneratedValue(strategy = GenerationTye.[   ])

 

1. IDENTITY

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

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

  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
  • AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행 한 이후에 ID 값을 알 수 있음
  • 원래는 em.commit()을 해야지 쿼리가 날라가지만 IDENTITY 전략은 em.persist() 시점에 영속성 컨텐츠에 넣기 위해 PK 값을 미리 알아야하는데 DB에 직접 넣어봐야 PK값이 생성된다.
    그렇기때문에 em.persist() 시점에 즉시 INSERT SQL 실행 하고 DB에서 식별자를 조회한다.
    => 덕분에 persist() 후 바로 PK를 알 수 있게 된다.

 

2. SEQUENCE

: 데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트(예: 오라클 시퀀스)
오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용

속성
설명
기본값
name
식별자 생성기 이름
필수
sequenceName
데이터베이스에 등록되어 있는 시퀀스 이름
hibernate_sequence
initialValue
DDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 1 시작하는 수를 지정한다.
1
allocationSize
시퀀스 한 번 호출에 증가하는 수로 성능 최적화에 사용됨
데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값 을 반드시 1로 설정해야 한다
50
catalog, schema
데이터베이스 catalog, schema 이름
 
  • IDENTITY는 em.persist() 시에 바로 INSERT 쿼리를 날렸다면은 SEQUENCE에서는 
    아래 사진과 같이 `call next value for []`을 통해 다음 PK 값만 딱 얻고 영속성 컨텍스트에 쌓아 두고 실제 commit 시점에 INSERT 쿼리를 날리게 된다.

  • 근데 위와 같이 처리한다 하더라도 매 persist() 마다 통신이 발생하기 때문에 성능 이슈를 고려하지 않을 수 없다.
    그렇다면 어떻게 저런 문제들을 해결할 수 있을까?
    이때 allocationSize이 쓰이게 된다. 
    예를 들어 initialValue = 1, allocationSize = 50 이라고 가정해보자.

    그렇게 되면 처음에 em.persist()를 한번하면 call next value for을 두번 호출하게 된다(두번 호출되는 이유는 allocationSize가 50인데 1이 나와서 그런것으로 예상된다?). 그러면 SEQ 값이 각각 1과 51일 될것이다.
    그럼 이제 추후에 em.persist()는 51전인 50까지는 확보된 상태이므로 memory값을 써서 2부터 쭉쭉 채워 나갈 것이고 next PK가 51이 되는 순간이 되어서야 다시 `call next value for`이 동작하게 된다.
    이는 트랜잭션당 데이터베이스와의 통신 횟수를 줄일 수 있으며, 이로써 성능을 향상시킬 수 있습니다. allocationSize가 크면 큰 범위의 키를 한꺼번에 미리 할당할 수 있으므로 통신 횟수가 줄어들고, 이는 네트워크 오버헤드를 감소시키고 성능을 향상

3. TABLE

키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉 내내는 전략
장점: 모든 데이터베이스에 적용 가능 

단점: 성능

잘 쓰진 않음.

 

Auto는 방언에 따라 위의 IDENTITY, SEQUENCE, TABLE 중에서 자동 지정해준다.

 

권장하는 식별자 전략

기본 키 제약 조건 : null이면 안되고 유일해야하며 변하면 안된다.
미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자!!
예를들어 주민등록번호 같은 것도 안된다. 무조건 대리키 사용

권장: Long형 + 대체키 + 키 생성전략 사용 !!!

 

 

실전 예제

  1. @Column(length = 10)을 통해 길이 제약을 걸어주면 개발자가 직관적으로 알 수 있다.
  2. @Table(indexes = @Index())를 통해 인덱스를 지정해주는 것이 좋다.
  3. `private Long memberId;`(FK)를 통해 테이블의 외래키를 객체에 그대로 가져오면서 DB 테이블 중심적으로 개발할 시에 객체 그래프 탐색이 불가능하며 참조가 없으므로 UML 연관관계도 다 끊킨다.
    => 그렇기 때문에 다른 테이블의 ID를 가지는 것이 아니라 그 객체 자체를 가진다.