반응형

JPA 관계 설정

자바 관점에서 생각하지 말고, RDB 관점에서도 생각해야한다.

 

JPA에서는 연관 관계의 주인이라는 개념이 있다.

연관 관계의 주인이란 외래 키를 관리하는 주체이다.

 

 

관점의 불일치

(1)

RDB에서는 한 곳에서 외래키를 갖고 있는데 자바에서는 양방향 매핑시 양쪽 모두 서로의 Entity를 갖고 있음

RDB에서는 해당 필드(칼럼)이 없는데 자바에는 존재

관점이 다르기 때문에 발생하는 문제로 RDB 외래키를 관리해주는 Entity를 누구로 할지 결정해야한다.

 

(2)

자바는 단방향 매핑이면 참조타입이 없는 한쪽에서 다른쪽으로 접근하는 것이 불가능,

RDB는 외래키를 이용해서 조인하면 서로 접근이 가능

 

 

 

Q&A

무조건 외래 키가 있는 쪽이 연관 관계의 주인 ?

 - 대부분의 경우엔 그렇다. 하지만 외래키가 없는 Entity가 관계의 주인이 될 수 있다.

 

외래키가 없는 곳을 굳이 연관 관계의 주인으로 지정하는 이유?

 - 편하다. 외래 키가 없음에도 연관 관계 CRUD가 완벽 동작

 

원래 외래키가 있는 곳에서의 CRUD는?

 - 양방향 매핑이라면 전부 정상 동작한다. 외래 키가 없는 곳에서 CRUD를 하기 위함이다.

 

단점은 없는지?

 - 외래 키 설정을 위한 추가 update 쿼리가 동작한다. (성능 저하)

 

mappedBy로 외래 키가 있는 반대편을 지정하는 이유?

 - 코드 가독성 측면에서 수많은 Entity를 볼 때 양방향 매핑이 돼있음을 알림,

   JPA에게 반대편 Entity가 주인임을 알림(매핑 테이블 생성과 검색을 방지),

   외래 키가 있는 곳에서만 외래 키를 관리하여 복잡도를 줄이기 위함

 

 


 

✔ 단방향 관계

한쪽만 매핑 어노테이션을 설정한 것

@ManyToOne, OneToMany 알맞게 설정하고

@JoinColumn으로 외래키를 갖고 있는 쪽을 가르킨다.

 

 

(1) 다대일 

RDB와 JAVA 관점을 일치시키는 방식.

RDB 외래 키가 있는 곳을 연관 관계의 주인으로 설정

 

(2) 일대다 

RBD와 JAVA 관점이 불일치되는 방식.

외래 키가 없는 곳에만 매핑 어노테이션 설정

외래키가 있는 Entity에는 매핑 어노테이션을 설정하지 않는다. 단순히 long boardId

RDB 외래키가 없는 곳을 연관 관계의 주인으로 설정

 

다쪽에 있는 외래 키를 수정하기 위해 추가적인 update쿼리가 동작한다.

이 방식보다는 차라리 양방향 매핑을 사용하므로, 많이 사용하는 방식은 아니다.

 

 

 

* 양방향에서의 관계 주인 (CRUD의 가능 여부)

✔ 양방향 관계

양쪽이 매핑 어노테이션을 설정한 것

모든 테이블을 양방향 관계로 설정하면 복잡성이 증가하므로 필요할 때만 양방향 관계 지정

 

사실 단방향 만으로도 Entity 간의 관계 설정은 완료된 것이다.

(RDB는 한쪽만 외래키를 갖는 식으로 완벽히 동작해왔다.)

 

 

 

✔ 외래 키가 없는 Entity에서 반대쪽 Entity의 외래 키를 인식하게 하는 방법은 두 가지

(1) mappedBy 

외래 키는 반대쪽 Entity가 갖고 있음을 알려준다.

 

mappedBy가 없으면?

RDB에서 외래 키가 없는 테이블에 외래 키를 생성하게 된다. (조회 필드를 외래 키로 잘못 인식해버린다.)

JPA가 양방향인 것을 모르고 해당 필드가 외래키로 인식되어 매핑 테이블을 생성하고, 그곳에서 관계 정보를 찾으려고 한다.

JPA가 만든 양쪽 Entity id를 갖는 매핑 테이블에는 당연히 아무런 정보가 들어가 있지 않아 찾지 못한다.

 

 

 

(2) JoinColumn 

반대쪽 Entity를 가리키면 연관 관계의 주인을 외래 키가 없는 곳으로 설정

이 방식을 권장하지 않는 이유는 추가적인 쿼리를 소모하게 된다.

두 Entity의 데이터를 새로 추가시킨다고 했을 때 외래키가 없는 Entity로 save()하면

 

Entity1 insert ---> Entity2 insert ---> 외래키가 있는 Entity를 update

 

insert 만으로 끝날 동작을, 반대편 Entity 외래 키를 update 하며 추가적인 쿼리가 소모된다.

성능을 희생하고, 편의를 증가

두 Entity 모두 CRUD가 가능해진다.

 


 

양방향 매핑 두 가지 방법

 

(1) JoinColumn(외래 키가 있는 곳에 설정), mappedBy(외래키가 없는 곳에 설정)

       -> mappedBy가 있는 Entity는 외래키를 관리하지 않는다.

 

연관 관계의 주인을 외래키가 있는 곳으로 설정, 권장되는 방식

외래키가 존재하는 Entity에서 연관 Create, Select, Update, Delete가 가능

mappedBy 쪽에서도 cascade 설정하면 Insert 빼고는 다 전이된다.

 

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
}         

 

 

 

 

(2) JoinColumn(외래 키가 없는 곳에 설정)

 

연관 관계의 주인을 외래키가 없는 곳으로 설정, 편의를 위해 성능을 희생시키는 방식

양쪽 모두 Create, Select, Update, Delete가 가능 (복잡도 증가)

 

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}

 

양방향 매핑 시에는 순환 참조, 순환 삭제 등 신경 써줘야 할 사항들이 생겨 복잡도가 증가한다.

그래서 mappedBy로 조회만 하고, 나머지는 연관 관계의 주인으로 처리하는 방식이 권장된다.

 

https://docs.jboss.org/hibernate/stable/annotations/reference/en/html/entity.html#entity-mapping-association

 

Chapter 2. Mapping Entities

In Section 2.4.5.1, “Lazy options and fetching modes” we have seen how to affect the fetching strategy for associated objects using the @Fetch annotation. An alternative approach is a so called fetch profile. A fetch profile is a named configuration a

docs.jboss.org

 

 


Cascade

연관관계 Entity에 대한 영속성 전이 설정

dafault는 아무것도 설정되어있지 않다.

 

persist - 연관 Entity를 영속성 콘텍스트에 manage 되게 전이시킨다.

merge - 연관 Entity를 update 할 수 있게 한다.

detach - 연관 Entity를 영속성 콘텍스트에서 관리하지 않는다.

remove - 연관 Entity를 함께 제거한다.

 

 

 

orphanRemoval

연관 Entity만 삭제하고 싶을 때 사용한다.

 

set...(null)을 할 경우 외래키가 없는 것으로 update 된다.

이때 자동으로 연관 설정이 없는 Entity를 DB에서 제거하고자 할 때 사용한다. 

(cascade는 set..(null)로 동작하지 않는다.)

 

 


Entity를 영속성 컨텍스트에서 사용할 때 연관 Entity 설정 (fetch type)

 

LAZY

가장 먼저 lazy를 사용하기 위해선 Entity가 영속성 컨테이너에 관리되어지고 있어야 한다.

연관 관계 Entity가 필요할 때만 get()으로 연관 Entity를 select를 통해 나중에 가져온다.

 

EAGER

연관 관계 Entity를 영속성 컨테이너로 전부 즉시 가져온다.

 

LAZY가 보편적 권장 사항

 

 

 

 

✔ N+1 

1대 N 관계에서 1인 Entity를 findAll()했을 경우 Many Entity와 조인해서 한번에 가져올 것을 기대한다.

 

하지만, 실제 동작은 1인 Entity를 먼저 select 한 후, 1인 Entity의 id를 이용해서

매번 from N where Entity_id = id(1인 Entity의 id값)로 N 테이블에서 여러 번의 select로 id값을 확인한다.

 

[1회] select로 id 개수 확인 ---> [N회] id 개수만큼 select 동작

 

 

여러 번의 select보다 조인을 사용하고 싶을 때

1.  jpql - join fetch 이용

(join과의 차이: 연관 Entity도 한번에 가져온다)

2.  @EntityGraph에서 join 할 Entity 지정

(중복 데이터가 있을 수 있다. distinct사용)

 

 

 

 

✔ sql select의 실행 횟수 vs 조인된 데이터의 양 

두 가지를 고려하여 어떤 것이 더 효율적인가를 고려해야 한다.

 

sql select의 실행 횟수가 너무 많다면, DB를 반복적으로 접근해서 IO가 발생, 느려진다. -> 조인을 하는 선택을 한다.

가져온 데이터의 양이 너무 과하면 -> sql select를 필요한 것만 수행, 연관 Entity 정보만 가져오는 것을 선택한다.

 


참고

 

https://catsbi.oopy.io/f3204fd9-954c-44d7-ab18-2ca56000c5e5

 

다양한 연관관계 매핑

연관관계 매핑시 고려사항 3가지

catsbi.oopy.io

 

반응형

+ Recent posts