알아두면 좋은 JPA 어노테이션들

Violet_Evgadn·2023년 4월 24일
0

DB연동

목록 보기
21/22

EntityListener

EntityListener란?

EntityListener란 특정 메서드에 어노테이션을 붙이면 어노테이션에서 명시된 시점에 해당 메서드가 호출되어 실행되는 것을 말한다.

예를 들어, 내가 View단에서 데이터를 보낼 때 [1,2,3,4] 같은 List 형식이 아닌 String 형식인 "1,2,3,4"로 데이터를 받았다고 가정하자.

그럴 경우 우리는 로직상 이 "1,2,3,4"를 List 형식인 [1,2,3,4]로 바꾸거나, 반대로 [1,2,3,4] List Data를 "1,2,3,4"로 변환하는 과정이 필요할 수도 있다.

그런데 이런 Case가 매번 실행되어야 한다면 같은 메서드를 계속해서 입력해줘야 한다는 것이고, 이는 객체지향적인 개발이 아닐 것이다.

따라서 EntityLister를 활용해 특정 메서드를 명시된 시점에 실행시켜 메서드의 중복 없이도 내가 원하는 로직을 실행하도록 하는 것이다.

EntityListener 종류

  • @PostLoad : 해당 엔티티를 새로 불러오거나 refresh 한 이후
  • @PrePersist : 해당 엔티티를 저장하기 이전
  • @PostPersist : 해당 엔티티를 저장한 이후
  • @PreUpdate : 해당 엔티티를 업데이트 하기 이전
  • @PostUpdate : 해당 엔티티를 업데이트 한 이후
  • @PreRemove : 해당 엔티티를 삭제하기 이전
  • @PostRemove : 해당 엔티티를 삭제한 이후

EntityListener 사용법

import com.example.demo.domain.BaseTimeEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@NoArgsConstructor
@Entity
@Table(name = "posts")
public class Posts extends BaseTimeEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="id")
    private Long id;

    @Column(length=500, nullable=false, name = "title")
    private String title;

    @Column(columnDefinition = "TEXT", nullable = false, name = "content")
    private String content;

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


	// 개인적인 취향. 필자는 생성자를 Builder 형식으로 지정하는 것을 좋아한다.
    @Builder
    public Posts(String title, String content, String author) {
        this.title = title;
        this.content = content;
        this.author = author;
    }

	// EntityLister 부분
    @PrePersist
    public void prePersist(){
        this.title = this.title==null?"없음":this.title;
    }
}

Embedded 및 Embeddable

내가 User 정보를 저장하고 싶다고 가정하자.

이 때 User 정보에서 "주소"라는 정보가 존재할 것이다.

그런데 이 "주소"라는 정보는 한 번에 객체 형식으로 다루는 것이 더욱 편하다.

예를 들어 내가 User의 우편 번호 및 주소를 알고 싶을 때 원래라면 User.zipCode와 user.address를 통해 알아내야 했지만, 이 둘을 Address라는 객체에 모아 User.address를 통해 한 번에 데이터를 뽑아와 처리할 수 있다면 더욱 좋을 것이다.

이때 활용하는 것이 @Embedded와 @Embeddable이다.

사실 @Embeddable은 생략 가능하지만 가독성을 위해 웬만하면 추가하는 것을 추천한다.

@Entity
public class User{
    @Id
    @GeneratedValue(startegy=GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    // 객체로 다루고 싶은 주소 정보
    @Embedded
    private Address address;
}

/////// 객체로 다룰 주소 정보에 대한 Entity ////////////

@Embeddable // 생략은 가능
public class Address{
    private String zipCode;
    private String address;
    private String detailAddress;
}

여기서 주의 깊게 봐야 하는 것은 이 @Embedded는 @OneToMany나 @ManyToOne처럼 따로 Table을 만드는 게 아니라 그냥 User Table에 ZIPCODE, ADDRESS, DETAILADDRESS라는 Column 3개가 한 번에 추가되는 것이며 데이터를 뽑아올 때도 Table의 ZIPCODE Column을 zipCode에 저장하는 등의 형식으로 데이터를 처리하는 것이다.


Super Sub Type 처리

아마 JPA의 장점 중 하나는 "패러다임 불일치 문제의 해결"일 것이다.

그리고 패러다임 불일치 문제로 가장 많이 거론되는 예시가 "Super타입과 Sub 타입"일 것이다.

JPA는 Annotation을 통해 이 문제를 해결하고 있다.

실제 사용법을 통한 이해

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="DTYPE")
public class Game {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;private String name;
    private int price;
}
@Entity
@DiscriminatorValue("S")
public class Starcraft extends Game{

}
@Entity
@DiscriminatorValue("L")
public class LOL extends Game{

}

먼저 위 예시를 보자면 Game이라는 큰 Table이 존재하고, 이 Table이 Suepr Type이 되는 것이다.

그리고 이 Super Type에 연동되는 Sub Type Table인 Starcraft와 LOL이라는 Table이 존재하게 될 것이다.

이제 우리는 Super와 Sub라는 DB의 특수한 패러다임을 객체 지향에서 매우 익숙한 상속 관계를 통해서 처리할 수 있게 되는 것이다.

사용되는 Annotation에 대한 설명은 아래와 같다.

  • @Inheritance(strategy=InheritanceType.XXX)
    • Super Sub Type 중 Super Type인 Table에 붙여주는 Annotation
    • 어떤 방식으로 상속 관계인 객체들을 처리할지 전략을 명시해주는 어노테이션
    • Inheritance Type 종류(상속 관계 처리 전략)
      • JOINED : 조인전략. Super 및 Sub Entity 모두를 각각 다른 Table로 만들어 Super Tabe에 어떤 Entity인지 명시해주는 Column을 만들어 구별하는 방식
      • SINGLE_TABLE
        • Default 전략
        • 테이블 하나만 활용하여 모든 상속 관계 데이터를 처리하는 방식
      • TABLE_PER_CLASS
        • Sub Entity, 즉 자식 Entity에 대해서만 Table을 만들어주는 전략
        • 일반적으로 장점이 매우 적고 굳이 상속 관계로 만들어 처리해줄 필요가 없으므로 활용하지 않음
  • @DiscriminatorColumn(name="DTYPE")
    • 부모 클래스에 선언하는 것으로 하위 클래스를 구분하는 용도로 활용될 데이터를 저장할 Column Name을 지정해준다.
    • Default 값은 "DTYPE"으로 설정됨
    • @DiscriminatorValue를 통해 Sub Type들을 구분하는데, 이때 @DiscriminatorValue를 통해 지정한 값이 저장되는 Column이다.
    • SINGLE_TABLE 전략을 활용할 경우 꼭 사용해야 하는 어노테이션
  • @DiscriminatorValue("XX")
    • 하위 클래스(자식 클래스) Entity에 사용되는 어노테이션으로 하위 클래스끼리 구별하기 위한 값을 설정할 수 있음
    • 위 예시처럼 Starcraft에 "S"로 저장했다면 부모 클래스 Entity Table의 DTYPE이라는 Table에 "S"라는 데이터가 저장되어 있다면 Starcraft 데이터를 처리하고 있는 Data라는 것을 알 수 있는 것이다.
    • Annotation을 설정하지 않을 경우 기본적으로 하위 클래스의 이름으로 설정된다.
profile
혹시 틀린 내용이 있다면 언제든 말씀해주세요!

0개의 댓글