Spring

Raw JPA 로 개발하기

sehunbang 2024. 3. 5. 15:02

도메인 모델과 테이블 설계 

  • User : 유저 정보로 채널과 관계만 양방향이고, 다른 도메인과는 단방향 관계를 가집니다.
  • Channel : 대화 채널은 유저와 다대다 관계를 가진다.
  • Thread : 채널내 대화 쓰레드로 Post 와 같이 댓글, 이모지, 멘션과 관계를 가진다.
  • Comment : 쓰레드내 댓글로 쓰레드와 다대일 관계를 가지며 이모지, 멘션과도 관계를 가진다.
  • Emotion : 쓰레드, 댓글내 이모지로 쓰레드, 댓글과 다대다 관계를 가집니다.
  • Mention : 쓰레드, 댓글내 멘션으로 쓰레드, 댓글과 다대다 관계를 가집니다.

 

Raw JPA 테이블 매핑 기능 (복습)

 

@Entity

  • 객체 관점에서의 이름
  • 디폴트로 클래스명으로 설정됨
  • 엔티티의 이름은 JQL에서 쓰임
    • JQL : Entity 명으로 쿼리짤때 쓰이는 언어 (ex. JPQL, QueryDsl)

@Table

  • RDB 의 테이블 이름
  • @Entity의 이름이 테이블의 기본값.
    • 주로 Entity 이름과 다르게 Table 명을 지정하고 싶을때 아래와 같이 사용

@Id

  • 엔티티의 주키를 맵핑할 때 사용.
  • 자바의 모든 primitive 타입과 그 랩퍼 타입을 사용할 수 있음
    • Date랑 BigDecimal, BigInteger도 사용 가능.
  • 복합키를 만드는 맵핑하는 방법도 있지만 그건 논외로..

@GeneratedValue

  • 주키의 생성 방법을 맵핑하는 애노테이션
  • 생성 전략과 생성기를 설정할 수 있다.
    • 기본 전략은 AUTO: 사용하는 DB에 따라 적절한 전략 선택
    • TABLE, SEQUENCE, IDENTITY 중 하나.

@Column

  • unique
  • nullable
  • length
  • columnDefinition
  • ...

@Temporal

  • 현재 JPA 2.1까지는 Date와 Calendar만 지원.

@Transient

  • 컬럼으로 맵핑하고 싶지 않은 멤버 변수에 사용.

example :

@Entity(name = "parent")
public class Parent {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToMany
  @JoinTable(
      name = "parent_child",
      joinColumns = @JoinColumn(name = "parent_id"),
      inverseJoinColumns = @JoinColumn(name = "child_id")
  )
  private List<User> childList;
}

 

 

 

Raw JPA 필드 타입 매핑 기능

value 타입 종류

  • 기본 타입
    • **@Column**
      • String, Date, Boolean, 과 같은 타입들에 공통으로 사이즈를 제한할 용도로 쓰인다.
      • Class 에 @Entity 가 붙어있으면 자동으로 필드들에 @Column 이 붙음
    • **@Enumerated**
      • Enum 매핑용도로 쓰이며 실무에서는 @Enumerated(EnumType.*STRING*) 으로 사용권장
      • Default 타입인 ORDINAL 은 0,1,2.. 값으로 들어가기 때문에 추후 순서가 바뀔 가능성있다.
  • Composite Value 타입
    • **@Embeddable**
    • **@Embedded**
    • **@AttributeOverrides**
    • **@AttributeOverride**
@Embeddable
public class Address {

  private String street;

  private String city;

  private String state;

  private String zipCode;

}

 

 

 

Raw JPA 매핑 기능 (복습)

@OneToOne

  • 일대일 관계를 나타내는 매핑 정보
  • 1:1 관계를 지정하기에 앞서 이것이 꼭 물리적으로 테이블이 분리되어야 하는지에 대해 생각해 봐야 합니다.
  • 1:1 관계로 구성 한다는 것은 결국 하나의 목적에 부합되는 공통된 데이타를 관리한다고 볼 수 있으며 이것은 하나의 테이블에서 관리 할 수 있는 데이타일 가능성이 높다는 의미입니다.
  • 즉, 의도적 중복이 아니라면 사용할일이 없다는 말
@Entity
public class Locker {
  @Id
  @GeneratedValue
  @Column(name = "LOCKER_ID")
  private Long id;
  private String name;
}
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;

 

 

 

@OneToMany (양방양 없음 ManyToOne 으로 할순 있지만 의미 없음)

  • 일대다 관계를 나타내는 매핑 정보
  • @OneToMany가 단방향으로 쓰이면 문제가 발생할 수 있다.
  • 속도를 위해 기본적으로 FetchType 설정이 LAZY 로 설정되어 있습니다.
  • 속성
    • mappedBy : 연관관계의 주인 필드를 선택한다.
    • fetch : 글로벌 페치 전략 설정
    • cascade : 영속성 전이 기능을 사용한다.
    • targetEntity : 연관된 엔티티의 타입 정보를 설정한다.
@Entity(name = "parent")
public class Parent {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @OneToMany
  @JoinColumn(name = "parent_id")
  private List<Child> childList;
}

 

@Entity(name = "child")
public class Child {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @Column(name = "parent_id")
  private Long parentId;
}

 

@ManyToOne

  • 다대일 관계를 나타내는 매핑 정보
  • 속성
    • optional (default true) : false로 설정하면 연관된 엔티티가 반드시 있어야 함.
    • fetch : 글로벌 패치 전략 설정
      • ✋ 기본이 EGEAR 로 설정되어있으나 실무에서는 기본 LAZY로 설정하는것 추천!
    • cascade : 영속성 전이 기능 사용
    • targetEntity : 연관된 엔티티의 타입 정보 설정 (targetEntity = Member.class 식으로 사용)

@JoinColumn

  • 외래 키 매핑 시 사용 (Join 을 요청하기 위한 매핑정보로 쓰인다.)
  • @ManyToOne 어노테이션과 주로 함께 쓰인다. (조인대상 컬럼 지정기능을 안쓸거면 생략해도 됨)
  • name 속성은 매핑할 외래키의 이름
  • 어노테이션을 생략해도 외래 키가 생성됨.
    • 생략 시 외래키의 이름이 기본 전략을 활용하여 생성된다.
  • 속성
    • name : 매핑할 외래 키의 이름
    • referencedColumnName : 외래 키가 참조하는 대상 테이블의 컬럼명
    • foreignKey : 외래 키 제약조건 지정 (테이블 생성 시에만 적용됨)
    • unique/nullable/insertable/updateable/columnDefinition/table : @Column의 속성과 같음
@Entity(name = "child")
public class Child {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToOne
  @JoinColumn(name = "parent_id")
  private Parent parent;
}

 

@ManyToMany (언제나 양방양) (실무에서는 OneToMany 로 Mapping table 만들어 사용)

  • 다대다 관계를 나타내는 매핑 정보 (N:M)
  • 다대다 설정을 하게되면 중간 매핑테이블(JoinTable)이 자동으로 생성된다.
  • 중간 매핑 테이블은 JPA상에서 숨겨져서(Entity 정의 없이) 관리된다.

예시.

@Entity
public class Child {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToMany
  @JoinTable(
      name = "parent_child",
      joinColumns = @JoinColumn(name = "parent_id"),
      inverseJoinColumns = @JoinColumn(name = "child_id")
  )
  private List<Parent> parents;
}
@Entity
public class Parent {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToMany(mappedBy = "parents")
  private List<Child> childs;
}
  • ✋ 매핑 테이블 관리가 불가능하여서 실무에서는 잘 사용하지 않는 기능 입니다.
    • 실무에서는 매핑 테이블을 아래와 같은 형태로 직접 정의합니다.
@Entity
public class Child {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @OneToMany(mappedBy = "child")
  private List<ParentChild> parentChilds;
}
@Entity
public class Parent {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @OneToMany(mappedBy = "parent")
  private List<ParentChild> parentChilds;
}
@Entity
public class ParentChild {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToOne
  @JoinColumn(name = "parent_id")
  private Parent parent;

  @ManyToOne
  @JoinColumn(name = "child_id")
  private Child child;
}

 

 

연관관계 매핑 심화

복합키(Forien Key) 사용하기

  • PK 1개 - 단일키
  • FK 2개 - 복합키 (PK 없이)
  • 복합키를 선언하는 방법은 2가지가 있습니다.
    1. @IdClass를 활용하는 복합키는 복합키를 사용할 엔티티 위에 @IdClass(식별자 클래스) 사용
      • @IdClass 복합키 실습코드 
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserChannelId implements Serializable {
  private Long user;   // UserChannel 의 user 필드명과 동일해야함
  private Long channel; // UserChannel 의 channel 필드명과 동일해야함

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    UserChannelId userChannelId = (UserChannelId) o;
    return Objects.equals(getUser(), userChannelId.getUser()) && Objects.equals(getChannel(), userChannelId.getChannel());
  }

  @Override
  public int hashCode() {
    return Objects.hash(getUser(), getChannel());
  }
}
@Entity
@IdClass(UserChannelId.class)
public class UserChannel {

  @Id
  @ManyToOne
  @JoinColumn(name = "user_id")
  User user;

  @Id
  @ManyToOne
  @JoinColumn(name = "channel_id")
  Channel channel;
}

 

 

  1. @EmbeddedId를 활용하는 복합키는 복합키 위에 @EmbeddedId 사용
    • @EmbeddedId 복합키 실습코드
@Entity
public class UserChannel {

  @EmbeddedId
  private UserChannelId userChannelId;


  @ManyToOne
  @MapsId("user_id")
  User user;

  @ManyToOne
  @MapsId("channel_id")
  Channel channel;

}
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Embeddable
public class UserChannelId implements Serializable {

  @Column(name = "user_id")
  private Long userId;

  @Column(name = "channel_id")
  private Long channelId;

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    UserChannelId userChannelId = (UserChannelId) o;
    return Objects.equals(getUser(), userChannelId.getUser()) && Objects.equals(getChannel(), userChannelId.getChannel());
  }
  
  @Override
  public int hashCode() {
    return Objects.hash(getUser(), getChannel());
  }

 

 

Raw JPA 기타 기능 (Cascade, orphanRemoval, Fetch)

Cascade (영속성 전이)

  • 사용 위치
    • 연관관계의 주인 반대편 - 부모 엔티티(다대일에서 )
    • 즉, @OneToMany 가 있는 쪽 또는 @OneToOne 도 가능
    • 예를들어, 게시글과 첨부파일이라면 에 해당하는 게시글에 설정한다.
  • 사용 조건
    • 양쪽 엔티티의 라이프사이클이 동일하거나 비슷해야한다.
      • 예를들어, 게시글이 삭제되면 첨부파일도 같이 삭제 되어야 한다.
    • 대상 엔티티로의 영속성 전이는 현재 엔티티에서만 전이 되어야 한다. (다른곳에서 또 걸면 안됨)
      • 예를들어, 첨부파일을 게시글이 아닌 다른곳에서 영속성 전이를 하면 안된다.
  • 옵션 종류
    • ALL : 전체 상태 전이
    • PERSIST : 저장 상태 전이
    • REMOVE : 삭제 상태 전이
    • MERGE : 업데이트 상태 전이
    • REFERESH : 갱신 상태 전이
    • DETACH : 비영속성 상태 전이
/**
 * Defines the set of cascadable operations that are propagated
 * to the associated entity.
 * The value<code>cascade=ALL</code>is equivalent to
 *<code>cascade={PERSIST, MERGE, REMOVE, REFRESH, DETACH}</code>.
 *
 *@since1.0
 */
public enum CascadeType {

  /** Cascade all operations */
  ALL,

  /** Cascade persist operation */
  PERSIST,

  /** Cascade merge operation */
  MERGE,

  /** Cascade remove operation */
  REMOVE,

  /** Cascade refresh operation */
  REFRESH,

  /**
   * Cascade detach operation
   *
   *@since2.0
   *
   */
  DETACH
}

 

 

orphanRemoval (고아 객체 제거)

  • 사용 위치
    • @OneToMany 또는 @OneToOne 에서 사용 - 부모 엔티티
  • 사용법
    • Cascade.REMOVE 와 비슷한 용도로 삭제를 전파하는데 쓰인다.
    • 부모 객체에서 리스트 요소삭제를 했을경우 해당 자식 객체는 매핑정보가 없어지므로 대신 삭제해준다.
      • 요건 DB 에서는 절대 알 수 없는 행동이다. (부모가 자식의 손을 놓고 버리고 간 고아 객체)
Parent parent1 = em.find(Parent.class, parent.getId());
parent1.getChildList().remove(0); // delete 쿼리나간다.

 

 

그렇다면 Cascade.REMOVE 와 orphanRemoval 차이점은 무엇인가?

 

Cascade.REMOVE의 경우 에 해당하는 부모 엔티티를 em.remove를 통해 직접 삭제할 때,그 아래에 있는 에 해당하는 자식 엔티티들이 삭제되는 것입니다.

orphanRemoval=true는 위 케이스도 포함하며,에 해당하는 부모 엔티티의 리스트에서 요소를 삭제하기만 해도 해당 에 해당하는 자식 엔티티가 delete되는 기능까지 포함하고 있다고 이해하시면 됩니다.

 

즉, orphanRemoval=true 는 리스트 요소로써의 영속성 전이도 해준다는 뜻

 

  • 옵션
    • true
    • false

✅ 영속성 전이 최강 조합 : orphanRemoval=true + Cascade.ALL

위 2개를 함께 설정하면 자식 엔티티의 라이프 사이클이 부모 엔티티와 동일해지며, 직접 자식 엔티티의 생명주기를 관리할 수 있게 되므로 자식 엔티티의 Repository 조차 없어도 된다. (따라서, 매핑 테이블에서 많이 쓰임)

 

 

 

Fetch (조회시점)

  • 사용 위치
    • Entity 에 FetchType 으로 설정할 수 있다.
      • @ElementCollection, @ManyToMany, @OneToMany, @ManyToOne, @OneToOne
    • Query 수행시 fetch Join 을 통해서 LAZY 인 경우도 즉시 불러올 수 있다.
  • 사용법
    • 기본 LAZY를 설정한 뒤에 필요할때만 fetch Join 을 수행한다.
    • 항상 같이 쓰이는 연관관계 일 경우만 EAGER 를 설정한다.
  • 옵션(FetchType)
    • EAGER : 즉시 로딩 (부모 조회 시 자식도 같이 조회)
    • LAZY : 지연 로딩 (자식은 필요할때 따로 조회)