Java/Spring & Spring Boot

JPA의 Entity 매핑 (feat. Auditable, GeneratedValue 전략)

마손리 2023. 4. 26. 18:14

Entity와 테이블간의 매핑

@Getter
@Setter
@Builder
@Entity // 1.
public class Member extends Auditable { // 8.
    @Id // 2.
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 3.
    private Long memberId;

    @Column(nullable = false, updatable = false, unique = true) // 4.
    private String email;

    @Column(length = 100, nullable = false, name = "FULL_NAME")
    private String name;

    @Column(length = 13, nullable = false, unique = true)
    private String phone;
	
    @Transient // 5.
    private String age;
    
    @Enumerated(value = EnumType.STRING) // 6.
    @Column(length = 20, nullable = false)
    private MemberStatus memberStatus = MemberStatus.MEMBER_ACTIVE;

    public enum MemberStatus { // 7.
        MEMBER_ACTIVE(1, "활동중"),
        MEMBER_SLEEP(2, "휴면 상태"),
        MEMBER_QUIT(3, "탈퇴 상태");
        @Getter
        private int statusNumber;
        @Getter
        private String statusDescription
        
        MemberStatus(int statusNumber, String statusDescription) {
           this.statusNumber = statusNumber;
           this.statusDescription = statusDescription
        }
    }
}
  1. @Entity - entity class에 선언해주며 JPA 관리 대상 엔티티로써 해당 클래스명과 같은 테이블로 매핑이된다. @Entity(name="USER")와 같이 name 어트리뷰트로 테이블의 이름을 따로 지정해 줄 수 있다.
  2. @Id - 필드에 추가해 주면 해당 필드는 DB에서 Primary Key로 선언된다. JPA에서는 @Entity와 @Id 에너테이션은 필수 이다. @GeneratedValue 에너테이션이 없다면 Id를 직접 할당해 주어야 한다.
  3. @GeneratedValue - Id 자동 생성 방식을 설정하는 에너테이션 . (밑에서 설명)
  4. @Column - 에너테이션을 이용하여 테이블의 컬럼으로 매핑이 된다. Entity객체에서 memberStatus라는 필드는 테이블에서 MEMBER_STATUS라는 column으로 자동 매핑되며 어트리뷰트를 통해 이름을 지정해 줄 수 있다.
  5. @Treansient - 해당 필드는 매핑을 하지않고 무시한다.
  6. @Enumerated - 열거형 타입의 필드와 매핑할 때 사용하며 value 어트리뷰트로 EnumType.ORDINAL(숫자 열거형) 혹은 EnumType.STRING(문자 열거형) 지정할 수 있다.
  7. 6번의 열거형 참조 타입이다. 해당 Enum에는 문자형과 숫자형 데이터 둘다 들어가 있지만 특별한 상황이 아니라면 EnumType.STRING(문자 열거형)으로 매핑하는 것을 권장한다. EnumType.ORDINAL(숫자 열거형)으로 할경우 해당 열거 타입을 수정하여 값이 바뀔시 수정 이전에 저장했던 데이터들에 버그가 나올수 있기 때문.
  8. Auditable - 생성날짜와 수정날짜 @Column 필드가 추가된 상속 클래스이다. (밑에서 설명) 

 

@GeneratedValue

Primary Key의 자동생성 기능을 사용하기 위한 에너테이션으로 strategy 어트리뷰트를 이용하여 4가지 전략중 하나를 선택할 수 있다.

 

GenerationType.IDENTITY 

PK값의 설정을 DB에 위임하는 전략 중 하나로 INSERT 쿼리문이 발생하면 DB에서 PK값을 지정해준다. 

 

하지만 이상한 점이 발생한다. JPA에서는 EntityManager.persist()실행시 Entity객체가 Persistence Context에 저장되고 EntityTransaction.commit()을 실행해야 비로서 쿼리문이 실행되는데 분명 Persistence Context에서 해당 Entity객체가 관리되려면 PK가 반드시 필요하지만 IDENTITY전략은 쿼리문이 실행되야 ID를 부여받을 수 있다. 

 

이것을 해결해주기 위해 IDENTITY 전략에서만 예외적으로 persist() 호출시점에 INSERT 쿼리문을 발생시켜 식별자를 받아와서 해당 Entity 객체에 부여해주고 Persistence Context에 저장한다. 

 

    private void example01(){
        tx.begin(); // EntityTransaction 실행
        Member member = Member.builder().email("test@gmail.com").build(); //엔티티 추가
        em.persist(member); // 영속성 컨텍스트에 추가
        
        Member resultMember1 = em.find(Member.class, 1L); // 이후 해당 엔티티 객체 검색
        System.out.println(resultMember1.getMemberId()+" "+ resultMember1.getEmail());
		// commit()은 실행 안함
    }
    
// 로그출력
Hibernate: insert into member (member_id, email) values (default, ?)
1 test@gmail.com

실제로 commit을 안함에도 INSERT 쿼리가 실행되었다.

 

GenerationType.SEQUENCE

IDENTITY 전략과 마찬가지로 DB에서 ID를 받아오지만 작동 방식이 조금 다르다.

SEQUENCE 전략은 persist 시점에서 Hibernate: call next value for hibernate_sequence를 실행시키면서 PK값을 DB에서 가저와 엔티티에 부여하고 해당 엔티티를 Persistence Context에 등록한다. 이후 commit을 통해서 INSERT 쿼리문이 실행된다.

//위와 같은코드지만 다른 출력
Hibernate: call next value for hibernate_sequence
1 test@gmail.com

PK값을 계속해서 DB에 따로 요구하기 때문에 성능상의 저하가 있을 수 있지만 @SequenceGenerator를 이용하여 해결 가능하다고 한다.

 

그외의 전략들

GenerationType.TABLE : 별도의 키 생성 테이블을 만들어 사용하는 전략

GenerationType.AUTO : JPA에서 위의 3가지 전략중 가장 적합한 전략을 자동으로 사용

 

(참고 자료 : https://gmlwjd9405.github.io/2019/08/12/primary-key-mapping.html )

 

 

Auditable 

@Getter
@MappedSuperclass // 1.
@EntityListeners(AuditingEntityListener.class) // 2.
public abstract class Auditable {
    @CreatedDate
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    @Column(name = "updated_at")
    private LocalDateTime updatedAt;
}

생성날짜 혹은 수정날짜 등 엔티티에 공통으로 들어갈 컬럼에대해 추상클래스로 만들어 주었다.

 

1. @MappedSuperclass

@Entity 클래스에서는 @Entity 혹은 @MappedSuperclass으로 선언된 클래스만 상속 받을수 있다. (@MappedSuperclass이 없는 부모클래스를 상속 받을시 어떠한 에러가 발생하는 것은 아니나 테이블과의 매핑시 어떠한 변화도 없다.)

 

@Entity와 다른점으로 @MappedSuperclass로는 테이블과의 매핑이 안되며 오로지 상속 클래스로 지정할때에 사용된다. 즉, 단순히 엔티티가 공통으로 사용하는 매핑 정보를 모으는 역할을 한다.

 

2. @EntityListeners

지정된 엔티티에 삽입, 수정, 삭제와 같은 이벤트가 발생했을때 콜백 메서드들을 실행 해주는 에너테이션이다. 

 

콜백 메서드 에너테이션

  • @PrePersist
  • @PreRemove
  • @PreUpdate
  • @PostPersist
  • @PostRemove
  • @PostUpdate
  • @PostLoad

 

위의 예시에서는 AuditingEntityListener 클래스에 정의된 콜백 메서드들을 실행해주며 AuditingEntityListener 클래스에는 @PrePersist와 @PreUpdate가 정의 되어있다.

 

즉, 전체적인 흐름은 사용자가 Coffee 엔티티를 생성 하게 되면 Coffee클래스가 상속받는 Auditable 클래스로 간뒤 AuditingEntityListener 클래스로 이동하고 이후 persist이벤트일경우 @PrePersist이 선언된 메서드가 실행되며 update이벤트일 경우 @PreUpdate메서드가 실행된다.

 

(참고 자료: https://docs.jboss.org/hibernate/stable/entitymanager/reference/en/html/listeners.html )