[JPA] 엔티티 매핑

2021. 7. 30. 21:56JPA

객체지향의 객체(Entity)와 데이터베이스의 테이블과 매핑하는 '엔티티 매핑'에 관하여

김영한님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편' 강의를 요약한 내용입니다.

 

https://www.inflearn.com/course/ORM-JPA-Basic

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔

www.inflearn.com

 

JPA에서 지원하는 Annotation

  • 객체와 테이블 매핑할 때: @Entity, @Table
  • 객체의 필드와 테이블의 컬럼 매핑할 때: @Column
  • 기본키(PK) 매핑: @Id
  • 연관 관계 매핑: @ManyToOne, @JoinColumn

 

@Entity

JPA가 관리하는 Entity라는 것을 지정해주기 위해 사용합니다.

일반 클래스에 해당 어노테이션을 붙일 수 있고,

final 클래스, enum, interface, inner 클래스에는 사용이 불가합니다.

또한 Entity로 지정된 클래스 내부에 final 필드는 사용이 불가능합니다.

@Entity
@Table(name = "MEMB")
public class Member {

    @Id
    private Long id;

    @Column(name = "name")
    private String username;

    private Integer age;

    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    @Lob
    private String description;

    @Transient
    private int temp;

}

@Entity 어노테이션외에도 다양한 어노테이션들이 있는데 아래에서 차근차근 설명해나가겠으니 지금은 보고 넘어가시면 됩니다.


데이터베이스 Schema 자동 생성

JPA를 알기 전으로 돌아가서, 특정 API를 만들었는데 DB에 아직 테이블이 조차 없으면 어떻게 했나요?

DB 클라이언트(MySQL Workbench나 Oracle Database Client)를 실행해서 테이블을 만드는 쿼리를 작성해서 실행시키고 테이블이 만들어진 것을 확인하면 API를 실행시켜서 개발자 의도대로 데이터가 들어갔는지 확인하는 과정을 거쳐야만 했습니다.

 

JPA를 사용하면 개발 과정에선 이런 번거로운 과정을 거치지 않아도 됩니다.

왜냐하면 애플리케이션 로딩 시점에 JPA가 알아서 DDL 쿼리를 DB에 날려서 Table을 생성해주기 때문입니다.

물론 JPA의 특성대로 데이터베이스 방언에 종속되지 않습니다.

아래 그림의 hibernate.hbm2ddl.auto 으로 해당 옵션을 부여할 수 있습니다. 

 

속성 종류

hibernate.hbm2ddl.auto의 속성 종류는 아래와 같습니다.

  • create: 기존 테이블 DROP후 다시 CREATE
  • create-drop: create와 같으나 종료시 테이블 DROP(테스트할 때 주로 사용)
  • update: ALTER만 적용. 단, column을 DROP하는 건 안됨(데이터는 유지됨)
  • validate: Entity와 Table이 정상 매핑되었지만 확인할 때 사용
  • none: 관례상 옵션 안쓰려면 none이라고 씀

 

Schema 자동 생성 사용시 주의점

위의 옵션을 사용할 때 주의할 것은 운영 장비에는 절대 create, create-drop, update를 쓰면 안된다는 것입니다.

운영 단계에서 해당 옵션을 켰다간 애플리케이션이 로딩될 때 DB가 DROP될 것입니다.

DB를 날려먹고 싶지 않다면, 아래를 참고하세요.

 

  1. 개발 초기 단계: create, update 허용
  2. 테스트 단계: validate, none 까지만 허용
  3. 운영 단계: validate, none 까지만 허용

결론적으로, 개발 초기 단계에선 사용하되 테스트, 운영 서버에선 직접 DDL을 사용하는게 좋습니다.

 

DDL 생성 기능

애플리케이션에 영향을 주는 것이 아닌 Database에만 영향을 주는 제약 조건 추가 등의 DDL문을 생성할 때 사용합니다.

JPA 실행 로직에는 영향을 주지 않습니다.

@Column(unique = true, length = 10)  //해당 컬럼에 unique, 길이 제한 제약 조건 추가 DDL 자동 생성
private String name;

필드와 컬럼 매핑

객체의 필드와 테이블의 컬럼을 매핑할 때 사용하는 어노테이션들을 알아보겠습니다.

@Entity
@Table(name = "MEMB")
public class Member {

    @Id
    private Long id;

    @Column(name = "name")
    private String username;

    private Integer age;

    @Enumerated(EnumType.STRING)
    private RoleType roleType;

    @Temporal(TemporalType.TIMESTAMP)
    private Date createdDate;

    @Temporal(TemporalType.TIMESTAMP)
    private Date lastModifiedDate;

    @Lob
    private String description;

    @Transient
    private int temp;

}

@Column(name = "name")

  • 필드명과 컬럼명이 다를 때 명시해줌

@Column의 속성

  • insertable, updatable: 자동 등록, 변경 가능 여부(default = true)
  • nullable: null 허용 여부, false면 not null 제약 조건 자동 생성(default = true)
  • unique: unique 제약 조건을 줄 수 있지만 제약 조건 이름을 지정할 수 없어서 @Table(uniqueConstraints = "제약조건 이름")를 쓰면 됨
  • length: 컬럼 크기 지정
  • columnDefinition: 추가적인 SQL문을 직접 작성  ex) "VARCHAR(100) DEFAULT 'EMPTY'"
  • precision, scale: 아주 큰 숫자 사용

 

@Enumerated(EnumType.STRING)

  • 자바의 enum을 사용하고 싶을 때 사용함(DB에는 Enum이 거의 없다)

@Enumerated의 속성

  • EnumType.ORDINAL: enum 순서를 데이터베이스에 저장(Default 값)
  • EnumType.STRING: enum 순서를 데이터베이스에 저장
  • ORDINAL을 쓰면 안됨 → Enum에 새로운 필드가 추가되는 순간 순서가 뒤바뀌어 버린다.

 

@Temporal(TemporalType.TIMESTAMP)

  • 날짜 타입 사용시 사용(DATE, TIME, TIMESTAMP 보통 DB는 세 가지)
  • JDK 1.8에 LocalDate, LocalDateTime이 등장해서 요즘엔 잘 안씀

 

@Lob

  • 문자면 CLOB(Character Large Object, 문자 대형 객체)으로 매핑
  • 그 외는 BLOB(Binary Large Object, 이진 대형 객체)으로 매핑

 

@Transient

  • 매핑 제외하기

기본키 매핑

기본키(PK)를 엔티티 클래스의 필드에 명시해주기 위해 @Id를 사용합니다.

 

기본키 매핑 전략 옵션의 종류

  • AUTO
    • JPA가 자동 생성
    • @Id
      @GeneratedValue(strategy = GenerationType.AUTO)
      private Long id;
  • IDENTITY
    • 기본키 생성을 DB에 위임
    • ex) MySQL: auto_increment, Oracle: sequence
    •  
  • SEQUENCE
    • sequence object를 생성하고 generate된 숫자를 매핑
    • ex) Oracle: sequence
    • @sequenceGenerator()를 쓰면 테이블별로 sequence 생성
    •  
  • TABLE
    • 키 전용 테이블을 하나 만들어서 generate
    • 성능 이슈(각각의 DB에서 지원하는 sequence나 auto_increment가 성능이 더 좋음)
    • 잘 안씀

PK의 제약 조건

  • not null
  • unique
  • 변하면 안 됨

 

권장하는 식별자 전략

권장하는 PK 생성 전략은 'PK는 비즈니스와 관련 없는 대체키를 쓰자'는 것입니다.

예를 들어서 PK를 주민번호로 쓰고 있었는데 법적인 이유로 PK를 다른 것으로 바꿔야하는 경우에,

이미 해당 PK를 사용해서 여러 테이블을 연관 관계를 이어 놓았는데 변경할 수 없게 되어 버립니다.

따라서 애초부터 PK는 비즈니스에 연관 없는 대체키를 사용해야 합니다.

 

PK는 int가 아닌 long형을 사용하는 것이 좋습니다. int형은 약 10억까지 수까지 밖에 표현하지 못합니다.

 

또한 각각의 DB에서 지원하는 키 생성 전략을 이용하는 것이 성능면으로 좋습니다.(Oracle의 sequence, MySQL의 auto_increment)

 

결국 PK는 long형 + 대체키 + DB에 맞는 키 생성 전략 사용 하는 것이 좋습니다.


IDENTITY 전략

JPA에선 1차 캐시에 쿼리들을 모아뒀다가 커밋시 한번에 보내게 되는데, PK가 IDENTITY 전략일 때는 DB에 값이 INSERT 되기 전까진 PK의 값을 알 수가 없습니다.

SELECT나 UPDATE, DELETE 작업을 수행하려면 PK가 꼭 필요합니다.

따라서 IDENTITY 전략일 때만 예외적으로 persist()를 호출할 때마다 내부적으로 INSERT 쿼리를 날려서 영속성 컨텍스트에서 해당 id의 객체를 관리합니다.

그로 인해 IDENTITY 전략일 때 버퍼링으로 인한 성능 이점은 얻기 힘드나, 어차피 한 트랜잭션 규모이기 때문에 효과는 미미합니다.

 

SEQUENCE 전략

initialValue와 allocationSize 속성에 의해(페이징의 offset, size와 비슷) DB에 추가된 후에야 PK값을 알 수 있습니다.

persist()시 PK값만 DB로부터 next call 해서 영속성 컨텍스트에서 관리하기 때문에, IDENTITY와 다르게 SQL문 버퍼링이 가능합니다.

 

잦은 (애플리케이션 서버 - DB 서버) 네트워크 통신으로 성능이 하락하지 않을까?

위의 PK 전략에 의해 애플리케이션 서버와 DB 서버간의 잦은 네트워크 통신이 발생하는데 이로 인해 성능 문제가 발생하지 않을까요?

이를 initialValue와 allocationSize 속성을 이용하면 영리하게 해결이 가능합니다.

initialValue = 1, allocationSize = 50으로 하면,

DB단에선 sequence가 1 ~ 50 까지 한번의 통신으로 미리 확보합니다.

애플리케이션단에선 1, 2, 3, 4, 5... 순으로 증가합니다.

DB 시퀀스 값과 애플리케이션 단의 수가 같은 시점에 다시 DB는 50 개의 시퀀스를 다시 확보하는 것을 반복합니다.

결국 미리 확보하는 방법으로 동시성 오류와 성능 문제를 해결해줍니다.

'JPA' 카테고리의 다른 글

[JPA] 영속성 관리 - 내부 동작 방식  (0) 2021.07.24
[JPA] JPA 시작하기  (2) 2021.07.23
[JPA] JPA란?  (0) 2021.07.22