개발하는 쿠키
article thumbnail

- 객체와 테이블 매핑: @Entity, @Table

- 기본 키 매핑: @Id

- 필드와 컬럼 매핑: @Column

- 연관관계 매핑: @ManyToOne, @JoinColumn

 

@Entity

JPA가 관리하는 클래스라는 의미이므로 필수로 붙여야 한다.

기본 생성자는 필수다.

final class, enum class, interface, inner class 에는 사용할 수 없다.

지정할 필드에 final을 사용하면 안된다.

 

@Table

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

 

다양한 매핑 사용

필드의 자료형마다 적절한 JPA어노테이션을 사용할 수 있다.

@Entity
@Table(name="MEMBER")
pulblic class Member {
    @Id
    @Column(name = "ID")
    private String id;
    
    @Column(name = "NAME")
    private String userName;
    
    @Enumerated(ENUMTYPE.STRING) // enum 타입
    private RoleType roleType;
    
    @Temporal(TemporalType.TIMESTAMP) // 날짜 타입
    private Date createdDate;
    
    @Lob // VARCHAR 타입
    private String description;
}

public enum RoleType {
    ADMIN,
    USER
}

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

JPA는 사용하는 데이터베이스에 맞게 스키마를 자동으로 생성한다.

<property name="hibernate.hbm2ddl.auto" value="create"/> 를 추가하면 애플리케이션 실행시점에 데이터베이스 테이블을 자동으로 생성한다.

지동생성되는 DDL은 완벽하지는 않으니 참고 정도로만 사용하는 것이 좋다.

 

DDL 생성 기능

@Column

@Column(name = "NAME", nullable = false, length = 10)
private String username;

@Column에 제약조건을 추가할 수 있다.

- nullable = false => 회원이름을 필수로 입력해야 한다.

- length = 10 => 이름은 10자를 초과하면 안된다.

 

@UniqueConstraint 

@Entity
@Table(name = "MEMBER", uniqueConstraints = {
    @UniqueConstraint(
        name = "NAME_AGE_UNIQUE",
        columnNames = {"NAME", "AGE"}
    )
})
public class Member {...}

NAME, AGE 컬럼에는 유니크 제약조건이 추가된다. 

하지만 이 제약조건은 DDL을 자동 생성할 때만 사용되고 JPA실행 로직에는 영향을 주지 않는다.

기본 키 매핑

JPA가 제공하는 데이터베이스 기본 키 생성 전략이다.

자바 기본형과 Wrapper형 자료형에 적용할 수 있다.

직접할당과 자동생성으로 생성 방식이 나뉜다.

직접할당 

- 기본키를 개발자가 애플리케이션에서 직접 할당한다.

자동생성

- IDENTITY: 기본 키 생성을 데이터베이스에 위임한다.

- SEQUENCE: 데이터베이스 시퀀스를 사용해서 기본키를 할당한다.

- TABLE: 키 생성 테이블 사용

 

기본 키 직접 할당 전략

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

...

Board board = new Board();
board.setId("id1"); // ID를 어플리케이션에서 직접 할당한다.
em.persist(board);

IDENTITY 전략

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

AUTO_INCREMENT 가 DDL에 추가돼서 데이터베이스가 기본 키를 자동으로 생성해준다.

엔티티가 영속 상태가 되려면 식별자 @Id가  반드시 필요하다. IDENTITY는 데이터베이스에 저장해야 기본 키를 알 수 있으므로 em.persist()를 호출하는 즉시 INSERT SQL이 실행된다. 

따라서 쓰기 지연이 동작하지 않는다.

IDENTITY 전략은 영속성 컨텍스트 1차캐시에 엔티티가 저장되는 시점이 언제일까?
JDBC3에 추가된 Statement.getGeneratedKeys()를 통해 DB와의 1번의 통신으로 데이터를 저장하면서 동시에기본 키를 가져올 수 있다.

그렇다면 저장과 동시에 1차 캐시에 기본 키를 이용해서 entity를 저장하는 걸까?
아니면 em.find() 실행 이후에 DB에서 기본키를 가지고 1차 캐시에 entity를 저장하는걸까?

아래 그림에서 왼쪽일까 오른쪽일까?

IDENTITY 전략에서 1차 캐시에 엔티티가 저장되는 시점

테스트코드로 테스트를 해보니 em.persist() 끝나자마자 객체에 ID가 세팅되어있다.

    @Test
    @DisplayName("JPA IDENTITY 1차 캐시 저장시점 테스트")
    void saveFistLevelCache() {
        Review review = Review
                .builder()
                .stars(5)
                .content("아무거나 리뷰 내용")
                .build();

        em.persist(review); // 이 시점에 Review 객체에 id가 세팅된다!
        System.out.println("리뷰 아이디: " + review.getReviewId());
    }

IDENTITY 전략일 때 1차 캐시에 entity가 저장되는 과정

그렇다면 위의 그림과 같이 추측해볼 수 있다.

1. em.persist(); -> 영속성 컨텍스트에 entity 등록

2. DB에 INSERT SQL실행

3. ID를 가져와서 영속성 컨텍스트의 entity에 ID 세팅

4. 1차 캐시에 entity 저장

영속성 컨텍스트와 애플리케이션 레벨의 entity는 같은 주소값을 가진 같은 객체다.
따라서 영속성 컨텍스트의 entity에 ID가 세팅되면 애플리케이션 레벨의 entity에서도 ID를 사용할 수 있다.
이를 JPA의 동일성 보장이라고 한다.

인프런 커뮤니티에 질문을 남겼는데 답변이 달리거나 공부해서 알게 되면 내용 추가하겠습니다.😀

SEQUENCE 전략

시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 데이터베이스에서 사용할 수 있다.

@Entity
@SequenceGenerator(
    name = "BOARD_SEQ_GENERATOR", // JPA 제너레이터 이름
    sequenceName = "BOARD_SEQ", // 데이터베이스 시퀀스 이름
    initialValue = 1,
    allocationSize = 1
)
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator = "BOARD_SEQ_GENERATOR")
    private Long id;
}

1. 데이터베이스 시퀀스를 조회한다.

2. 엔티티에 시퀀스 식별자를 할당한다.

3. 엔티티를 영속성 컨텍스트에 저장한다.

4. 트랜잭션을 커밋한다.

5. 플러시가 발생하면 엔티티 저장 SQL을 데이터베이스에서 실행한다.

TABLE 전략

키 생성 전용 테이블을 만들고 이름, 값 컬럼을 추가해서 데이터베이스 시퀀스를 흉내내는 전략이다.

create table MY_SEQUENCES (
    sequence_name varchar(255) not null,
    next_val bigint,
    primary key (sequence_name)
)
@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;
    ...
}

키 생성기를 사용할때마다 데이터베이스 컬럼 next_val 값이 1씩 증가한다.

 

AUTO 전략

데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 선택한다.

@Entity
public class Board {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO) // 기본값이기 때문에 괄호 생략 가능.
    private Long id;
    ...
}

기본 키 매핑 정리

em.persist() 호출한 직후 발생하는 일들

직접할당

em.persist() 호출 전 개발자가 직접 식별자 값을 할당해야 한다.

자동생성

- IDENTITY: 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다. 데이터베이스 쓰기 지연이 동작하지 않는다. 

- SEQUENCE: 데이터베이스 시퀀스에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.

- TABLE: 데이터베이스 시퀀스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다.

 

필드와 컬럼 매핑: 레퍼런스

매핑 어노테이션 설명
@Column 컬럼을 매핑한다.
@Enumerated 자바의 enum 타입을 매핑한다.
@Temporal 날짜 타입을 매핑한다. 자바에서 Date, Calendar class를 사용했을 때 적용했던 어노테이션이다. 
데이터베이스의 timestamp 형식과 매핑하기 위해 사용했다.
@Lob BLOB, CLOB 타입을 매핑한다.
매핑하는 필드 타입이 문자면 CLOB으로 매핑한다.
매핑하는 필드 타입이 이미지 또는 동영상이면 BLOB으로 매핑한다.
@Transient 특정 필드를 데이터베이스에 매핑하지 않는다.
데이터베이스에 값을 저장하지 않고, 조회하고 싶지 않을 때 사용한다.
거의 사용하지 않는다.
@Access JPA가 엔티티에 접근하는 방식을 지정한다.
@Access(AccessType.FILED) : 모든 필드에 접근한다. (private 필드도 접근 가능하다.)
@Access(AccessType.PROPERTY): 접근자 @Getter를 사용해서 프로퍼티에 접근한다.
JAVA 8부터 날짜 타입으로 LocalDate, LocalDateTime을 사용하게 되면서 @Temporal 어노테이션을 생략해도 된다.

 

정리

데이터베이스가 제공하는 기본키 전략에는 직접할당, 자동할당 (SEQUENCE, IDENTITY, TABLE) 이 있다.

 


4장 실습예제 코드는 https://github.com/mjkim103301/jpa-practice 여기를 참고하면 됩니다.

책과는 다르게 gradle, application.properties 를 사용했고, test코드를 사용해서 서비스 코드를 테스트했습니다.

 


참고자료

- https://www.yes24.com/Product/Goods/19040233 "자바 ORM 표준 JPA 프로그래밍-김영한"

반응형
profile

개발하는 쿠키

@COOKIE_

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!