JPA 연관관계

반영환·2023년 5월 29일
0

스프링 이모저모

목록 보기
9/12
post-thumbnail

JPA 연관관계

N:M은 사용하지 말 것

두 Entity 사이를 연결해주는 Relative Entity의 경우 역할이 연결에만 있는 것이 아니기 때문이다.

연결 Table을 연결 Entity로 승격

Member

@Entity
public class Member {
  ...
       
   @OneToMany(mappedBy = "member")
   private List<MemberProduct> memberProducts = new ArrayList<>();...
}

Product

@Entity
public class Product {...@OneToMany(mappedBy = "product")
   private List<MemberProduct> members = new ArrayList<>();
   
  ...
}

MemberProduct

@Entity
@Getter
@Setter
public class MemberProduct {@Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;@ManyToOne
   @JoinColumn(name = "member_id")
   private Member member;@ManyToOne
   @JoinColumn(name = "product_id")
   private Product product;
}

@Joincolumn

Foreign Key를 설정하는 부분이다. name 옵션은 단순 컬럼의 이름을 지정하는 것이므로 연관 관계와는 아무 연관 없다.

작동 원리

@JoinColumn 내부

    /**
     * (Optional) The name of the column referenced by this foreign
     * key column. 
     * 
     * When used with entity relationship mappings other
     * than the cases described here, the referenced column is in the
     * table of the target entity. 
     
     * When used with a unidirectional OneToMany foreign key
     * mapping, the referenced column is in the table of the source
     * entity.  
     * 
     * When used inside a <code>JoinTable</code> annotation,
     * the referenced key column is in the entity table of the owning
     * entity, or inverse entity if the join is part of the inverse
     * join definition.  
     * 
     * When used in a <code>CollectionTable</code> mapping, the
     * referenced column is in the table of the entity containing the
     * collection.
     * 
     *
     * Default (only applies if single join column is being 
     * used): The same name as the primary key column of the 
     * referenced table.
     */
    String referencedColumnName() default "";

한 줄 요약
referencedColumnName 속성을 생략하면 자동으로 대상 테이블의 pk 값으로 지정 된다.

@xxxToxxx Options

mappedBy

  • mappedBy 옵션을 지정한 쪽은 ReadOnly가 돼 관계에서 테이블의 종이 된다.
    반대로 지정하지 않은 쪽은 주가 된다.

  • 외래키를 가지고 있지 않은 쪽을 종으로 설정하는 것이 좋다.

  • mappedBy에는 반드시 주인(자식)이 가지고 있는 객체의 이름 ( alsoCounter O / also_counter X ) 로 넣어야 한다.

사용 이유

양방향 맵핑을 사용할 경우 두 객체 모두 필드를 통해 서로의 객체에 접근 가능하기 때문에 두 객체 중 하나의 객체만 테이블을 관리할 수 있도록 정하는 것이 mappedBy 옵션이다.

cascade

Entity의 상태 변화를 전파시키는 옵션이다.
주종 관계에 있는 도메인에 적용할 수 있다.

종 도메인에 설정한다.

@Entity
@Getter
@NoArgsConstructor
@Table(name = "also_user")
public class AlsoUser extends BaseTimeEntity implements UserDetails {

    ...

    @OneToMany(
            mappedBy = "alsoUser",
            cascade = {CascadeType.PERSIST, CascadeType.REMOVE},
            orphanRemoval = true
    )
    @JsonManagedReference
    private List<Photo> photos = new ArrayList<>();
    
    ...
    
}
  • CasecadeType.PERSIST : record 생성 시 설정이 적용된 인스턴스가Transient에서 Persistent 로 넘어갈 때 연관된 Entity 또한 같이 상태가 넘어가며 저장된다.

  • CasecadeType.REMOVE : 동일한 알고리즘으로 인스턴스의 Record가 삭제시 연관된 Entity Record 또한 같이 삭제된다.

Entity Status

  1. Transient : 객체를 생성하고, 값을 주어도 JPA나 hibernate가 그 객체에 관해 아무것도 모르는 상태. 즉, 데이터베이스와 매핑된 것이 아무것도 없다.

  2. Persistent : 저장을 하고나서, JPA가 아는 상태(관리하는 상태)가 된다. 그러나 .save()를 했다고 해서, 이 순간 바로 DB에 이 객체에 대한 데이터가 들어가는 것은 아니다. JPA가 persistent 상태로 관리하고 있다가, 후에 데이터를 저장한다.(1차 캐시, Dirty Checking(변경사항 감지), Write Behind(최대한 늦게, 필요한 시점에 DB에 적용) 등의 기능을 제공한다)

  3. Detached : JPA가 더이상 관리하지 않는 상태. JPA가 제공해주는 기능들을 사용하고 싶다면, 다시 persistent 상태로 돌아가야한다.

  4. Removed : JPA가 관리하는 상태이긴 하지만, 실제 commit이 일어날 때, 삭제가 일어난다.

orphanRemoval

종 Entity의 변경이 있다면, JPA에서는 insert -> update -> update -> delete
의 순서로 이어지는데 변경된 종 Entity를 insert하고, 기존 종 Entity를 Null로 업데이트한다.

이 과정에서 PK(JoinColumn) 값이 null로 변한 종 Entity는 고아 객체라고 하며 이 고아 객체를 삭제해주는 옵션이 orphanRemoval = true 이다.

순환 참조 방어

클라이언트에게 객체를 응답하는 경우 객체 간 연관관계가 물고 물려서 중복된 내용이 매우 많이 생겨난다.

@JsonManagedReference

mappedBy 가 적용된 곳에 적용 시킨다.

@JsonBackReference

mappedBy 가 적용되지 않은 곳에 적용 시킨다.

/*
* Not Mapped
*/
@ManyToOne
@JoinColumn(name = "user_id")
@JsonBackReference
private AlsoUser alsoUser;

/*
* Mapped
*/
@OneToMany(
    mappedBy = "alsoUser",
    cascade = {CascadeType.PERSIST, CascadeType.REMOVE},
    orphanRemoval = true
)
@JsonManagedReference
private List<Photo> photos = new ArrayList<>();

출처

순환 참조와 해결방법
[JPA] @JoinColumn 확실히 알고가기!!!
JPA orphanRemoval 용도
[JPA]엔티티 상태 & Cascade

profile
최고의 오늘을 꿈꾸는 개발자

0개의 댓글