Java

[JPA(Jakarta Persistence API)] 연관관계 매핑

ride-dev 2024. 3. 12. 19:24

연관관계

JPA(Jakarta Persistence API)에서 연관관계란 엔티티 간의 관계를 말하며,

외래 키를 통한 참조 관계를 클래스 간의 참조로 다룰 수 있게 해줍니다.

연관관계 매핑

JPA(Jakarta Persistence API)는 객체의 참조와 테이블의 외래 키를, 연관관계 매핑으로 처리합니다.

연관관계에는 방향(Direction), 다중성(Multiplicity), 연관관계 주인(Owner)이 있습니다.

 

연관관계의 방향에는 단방향과 양방향이 있고,

연관관계의 다중성에는 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)가 있습니다.

그리고 객체 양방향 연관관계를 관리하는 주인(Owner)이 있습니다.

 

아래 예시를 통해 단방향 연관관계와 양방향 연관관계를 살펴보겠습니다.

DB테이블에서는 외래 키를 통해 양측의 정보를 확인할 수 있습니다.

자바 객체에서는 연관관계를 설정하여 다른 엔티티의 정보를 확인할 수 있습니다.

작성자는 여러 개의 게시글을 작성할 수 있으며, 각 게시글에는 한 명의 작성자가 있습니다.

좌: 단방향 연관관계, 우: 양방향 연관관계

단방향 연관관계

한 엔티티가 다른 엔티티를 참조할 수 있지만, 반대 방향으로는 참조할 수 없는 관계를 말합니다.

(한 쪽에서만 모든 정보를 알 수 있습니다)

아래의 경우, Post엔티티에서 User엔티티를 확인할 수 있습니다.

하지만, User엔티티에서는 Post엔티티를 확인할 수 없습니다.

@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;

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

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;
}

양방향 연관관계

단방향 연관관계를 양쪽으로 설정하여 양방향 연관관계로 만듭니다.

양 쪽 엔티티가 서로 참조할 수 있습니다.

(서로의 정보를 알 수 있습니다)

@Entity
public class Post {
    @Id
    @GeneratedValue
    private Long id;

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

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    @OneToMany(mappedBy = "user")
    private List<Post> posts = new ArrayList<>();
}

(리스트를 new ArrayList<>() 로 초기화하는 것으로 NullPointException을 방지합니다)

위 예제에서 User를 조회하면 User가 작성한 Post를 확인할 수 있습니다.

(user_id만 알고 있다면 단방향 연관관계에서도 동일하게 조회하는 것이 가능합니다)

연관관계의 주인

양방향 연관관계를 설정하면,

한 쪽을 연관관계의 주인(Owner)으로 설정해야 하며, 주인이 외래 키를 관리합니다.

따라서 위 예제에선 Post 엔티티가 user_id라는 외래 키를 관리하고 있기 때문에 양방향 연관관계의 주인입니다.

(반대로, 두 개의 엔티티 중 테이블의 외래 키를 관리할 곳을 주인으로 지정합니다)

양방향 연관관계 주인 엔티티의 값을 변경할 때,

다른 엔티티의 값을 설정하는 것으로 순수한 객체 상태를 유지하도록 합니다.

 

연관관계 설정 메서드를 생성하여 편의성을 높일 수도 있습니다.

(주인이나 주인이 아닌 엔티티에 생성합니다, 일관성이 중요합니다)

public class Other{
    @Id
    private Long id;
    @ManyToOne
    @JoinColumn(name = "OWNER_ID")
    private Owner owner;
    
    public void changeOwner(Owner owner) {
        this.owner = owner;
        owner.getOthers().add(this);
    }
}

양방향 매핑 시 무한 루프를 주의합니다.

(toString(), Lombok)

 

양방향 관계를 설정하여 다양한 비즈니스 로직을 설정할 수 있지만,

복잡하지 않도록 하기 위해 단방향 관계를 적용하는 것이 좋습니다.

특정 비즈니스 로직에 양방향 관계가 필요하다면, 그 때 추가하는 것이 좋습니다.

다양한 연관관계

다대일 N:1, 일대다 1:N, 일대일 1:1, 다대다 N:M

다대일 N:1

다대일 단방향 연관관계는 가장 많이 사용하는 연관관계입니다.

다대일 양방향 연관관계에서 "다"에 해당하는 엔티티를 주로 주인으로 설정하여 외래 키를 관리합니다.

다대일 단방향 연관관계

post를 수정하면, post를 변경합니다.

일대다 1:N

일대다 단방향 연관관계는 주로 사용하지 않는 연관관계입니다.

엔티티에 외래 키를 사용하지 않고 List를 사용합니다.

일대다 양방향 연관관계에서 "다"에 해당하는 엔티티를 주로 주인으로 설정하여 외래 키를 관리합니다.

@JoinColumn을 사용해야 합니다.(혹은 조인 테이블 방식을 사용합니다)

 

객체 관점으로 생각하면 일대다 단방향 연관관계가 가능하지만,

DB 관점에서는 일대다 연관관계는 관리가 복잡해질 수 있습니다.

일대다 단방향 연관관계

post를 수정하면, post와 user 둘 다 변경해야 합니다.

따라서 일대다 연관관계보다 다대일 연관관계를 선호합니다.

일대일 1:1

주 테이블이나 대상 테이블 중에 외래키를 관리하도록 선택할 수 있습니다.

외래키에 유니크 제약조건을 추가합니다.

주 테이블이 외래키를 관리하도록 하는 것은 다대일 연관관계와 유사합니다.

대상 테이블이 외래키를 관리하도록 하는 것은 양방향만 지원합니다.

 

 주 테이블에 외래키를 두면 JPA매핑이 편리해지고, 주 테이블 조회만으로도 대상 테이블 데이터를 확인할 수 있지만,

값이 없으면 외래키에 null이 삽입됩니다.

 대상 테이블에 외래키를 두면 일대일에서 일대다 관계로 변경할 때 편리하지만, 프록시 기능 한계로 인해 지연 로딩을 지원하지 않습니다.(즉시 로딩됩니다)

다대다 N:M

한 명의 회원이 여러 개의 상품을 주문할 수 있고, 하나의 상품이 여러 회원에게 주문될 수 있지만,

관계형 데이터베이스는 정규화 테이블 2개로 다대다 관계를 표현할 수 없기 때문에,

중간에 연결 테이블을 추가하여 일대다, 다대일 관계로 풀어내야합니다.

만약 ManyToMany를 사용한다면, 연결 테이블에 필드를 추가할 수 없기 때문에,

회원 - 주문 - 상품

위와 같이 연결 테이블을 엔티티로 승격하여 일대다, 다대일 연관관계로 연결하여 사용합니다.

728x90