[자바 ORM 표준 JPA 프로그래밍 - 기본편] 엔티티 매핑 (1)

이재표·2023년 9월 22일
0

jpa에서 가장 중요한 2가지는 다음과 같다.

  1. 영속성 컨텍스트와 같은 jpa의 내부 매커니즘
  2. 객체와 관계형 데이터베이스를 매핑하는 설계적 측면

이번편에서는 2번째 객채와 관계형 데이터베이스를 매핑하는 부분에 대해 살펴보도록 하겠습니다.

엔티티 매핑 소개

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

@Entity

  • @Entity가 붙은 클래스는 JPA가 관리하며 엔티티라 한다.
  • JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수
    주의
  • 기본 생성자 필수 (jpa를 구현해서 쓰는 다양한 기술을 사용하기 위해 규격으로 생성자가 필수이다.)
  • final클래스, enum, interface, inner클래스 사용안됨
  • 저장할 필드에 final 사용불가 (final에 대한 자세한 내용은 게시글 참고!)

    @Table(name="")으로 관계형데이터베이스와 매핑할때 테이블의 이름을 작성할수 있음

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

필드의 있는 값을 바탕으로 데이터베이스가 매핑되기에, 매핑뿐만 아닌 실행시점에 jpa에서 테이블을 만들어주는 기능도 지원한다(DDL자동생성기능).

데이터베이스 방언에 맞춰 적절한 DDL을 생성해주기에 매우 편리하지만 운영서버에서 사용을 지양해라!

ddl은 persistence.xml을 통해 설정이 가능하다.

<property name="hibernate.hbm2ddl.auto" value="create" />
// create는 해당하는 테이블을 삭제하고, 생성하는 옵션

Member테이블이 존재한다면 삭제하고, 생성하는 쿼리를 볼수 있다.
-> 운영서버에서 이렇게 된다면 데이터가 다 날아가게되기때문에 지양한다.

Hibernate: 
    drop table Member if exists
    Hibernate: 
------------------------------ 
create table Member (
	id bigint not null,
	name varchar(255),
	primary key (id)
    )

그 외의 속성

create-drop : 생성했다 지우기때문에, 테스트를 진행할때 주로 사용
update : 추가되는 필드는 컬럼 추가가되지만, 존재하던 필드를 삭제한 것은 반영되지 않는다
validate : 현재 존재하는 테이블과 같은지 확인
none : ddl을 사용하지 않을때 사용하는데, 사실 none이라는 옵션이 있는게 아닌 위의 종류 외의 아무문자나 적으면 매칭되는게 없어서 실행되지 않는것

주의

운영장비에는 절대 create, create-drop, update를 사용하면 안된다.

  • 개발초기단계에서는 create 또는 update로 로컬이나 개발서버에서 사용
  • 테스트 서버는 update 또는 validate를 사용(create사용하면 안됨, 테스트 데이터 모두 삭제되지 때문)
  • 스테이징과 운영서버는 validate 또는 none
    가능하면 쓰지말기.
    ->데이터가 몇천만건 있는 상태에서 update가 되면 alter되면 자칫하면 db락이 걸린다. 테스트 서버나 개발서버에도 가능하면 직접 짜는게 권장함

DDL생성기능

DDL생성에만 영향을주기때문에 JPA 실행에는 영향을 주지않는다. 하지만 @Table과 같은 어노테이션은 쿼리를 날릴때 실제 TABLE과 매핑하는 과정등에서 사용되기 때문에 jpa실행에 영향 끼침

필드와 컬럼 매핑

실제 상황을 가정하여 진행

멤버

@Entity
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을 통해 설정가능)
  • @Enumerated(EnumType.STRING) : 객체에서 ENUM타입을 쓰고싶을때, DB에는 ENUM타입이 기본적으로 없기때문에 해당 어노테이션을 통해 사용
  • @Temporal : 세가지 타입(date,time,timestamp)이 있다. 자바는 date타입에는 날짜와 시간이 전부 다 있지만, db는 3가지를 구별해서 사용
  • @Lob : 데이터베이스에 varchar를 넘어서는 큰값을 사용하고싶을때 사용
  • @Transient : 특정 필드를 컬럼에 매핑하지 않을때, db와 상관없이 메모리에서만 무언가를 하고싶을때 사용

어노테이션 자세한 설명

@Column

  • nullable을 false를 하게되면 NotNull제약조건이 걸리게됨
  • 간단하게 unique제약조건을 만들고싶을때 unique옵션을 쓰지만, 랜덤의 긴 문자열이 제약값으로 되기때문에 운영에서 사용이 힘듦. 따라서 @Table의 옵션으로 uniqueConstraints를 통해 제약조건을 이름까지 설정할수 있게된다.

@Enumerated

기본이 Ordinal이지만 Ordinal사용 X
-> 요구사항이 달라져서 enum값이 추가되거나 삭제되었을때 인덱스가 달라져서 값의 혼동이 올수있기에, Oridinal사용금지

@Temporal

@Lob

@Transient

기본키 매핑

기본키매핑
@Id
@GeneratedValue
두가지 어노테이션을 사용한다.

기본키 매핑 방법

자동할당

IDENTITY 전략

  • 기본키 생성을 db에 위임
  • 주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용
    (예: MySQL의 AUTO_ INCREMENT)
  • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행
  • AUTO_ INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID 값을 알 수 있음
  • IDENTITY 전략은 em.persist() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회
  • DB가 자동으로 1부터 순서대로 id가 할당함
@Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id; 

Identity전략은 DB에 insert를 해야 값을 알수 있게된다. 그렇다면 의문이 생기게 된다.

jpa는 영속성 컨텍스트를 통해 객체를 관리하게되는데 이때 pk값이 무조건 있어야하는데, 그러면 영속성 컨텍스트에서 어떻게 관리하지?

그래서 Identity전략사용시 em.persist()를 통해 영속성 컨텍스트에 저장하기 전 DB에 insert쿼리를 날려 저장시킨다. 그리고 바로 DB에서 pk값과 함께 불러오는데, 이때 select 쿼리는 날아가지 않는다. 왜냐하면 JDBC가 저장한 값을 return받는 알고리즘이 짜여있어서 select쿼리를 사용하지 않아도 괜찮다. 하지만 이런 방식때문에 Identity는 모아서 한번에 버퍼하여 보낼수 없다.

Sequence전략

  • 오라클에서 자주사용됨
  • 시퀀스 오브젝트를 통해 값을 가져와서 설정함
  • Integer는 10억쯤되면 순서가 한번 돌게됨. Long을 사용하는게 유리.

    각 객체의 시퀀스를 다르게 관리하고싶을때 @SequenceGenerator를 이용하면 sequeceName에 따라 테이블을 따로 매핑해준다.

    @SequenceGenerator(
            name = "MEMBER_SEQ_GENERATOR",
            sequenceName = "MEMBER_SEQ",
            initialValue = 1,
            allocationSize = 1
    )
    public class Member {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE
                ,generator = "MEMBER_SEQ_GENERATOR")
        private Long id;
        @Column(name = "name",nullable = false)
        private String username;
    }

시퀀스의 값도 결국 DB에 가봐야 알수있다.
따라서 em.persist()실행시, DB에서 시퀀스의 값을 먼저 가져와서 객체에 값을 넣어주고 영속성 컨텍스트에 저장하게된다. 그리고 커밋 시점에 insert쿼리가 날아가게됨. insert쿼리를 바로 날리는 Identity전략과 달리 시퀀스의 경우 버퍼에 저장했다가 한번에 보내는거 가능하다.

이때 성능상의 걱정이될수있다(네트워크를 계속 타야하기 때문에).

그래서 @SequenceGenerator의 옵션에 allocationSize의 설정을 통해 한번에 설정된 만큼의 시퀀스의 크기를 가져올수 있다. 호출시 DB의 시퀀스가 시퀀스 크기만큼 올린다. 여러 서버가 동시에 호출하여도 그냥 크기만큼 주고, 올리기때문에 크게 상관없다.

Table전략

  • 키색성 전용테이블을 하나 만들어서 DB 시퀀스를 흉내내는 전략
  • 장점 : 모든 DB에 적용가능
  • 단점 : 성능 (최적화가 안되어있기 때문)
@Entity
@TableGenerator(
        name = "MEMBER_SEQ_GENERATOR",
        table = "MY_SEQUENCES",
        pkColumnValue = "MEMBER_SEQ", allocationSize = 1)
public class Member {
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE
            ,generator = "MEMBER_SEQ_GENERATOR")
    private Long id;
    @Column(name = "name",nullable = false)
    private String username;
}


다음과 같이 생성된것을 볼수있다.
운영에서 사용하기는 힘들다.DB에서 관례로 쓰는것들이 있기때문에 굳이 사용할필요 없다.

권장하는 식별자 전략

  • 기본키 제약 조건 : null이면 안된다. 유일해야한다. 변하면 안된다.
  • 미래까지 변하지않는 조건을 만족하는 자연키(비즈니스 로직적으로 사용되는 값(eg. 주민번호))는 찾기 힘들다. 대리키(대체키(비즈니스로직과 상관없는 키))를 사용하자!!
  • 권장 : Long형 + 대체키 + 키 생성전략 사용

0개의 댓글