[JPA] 연관 관계

giggle·2023년 7월 12일
0
post-custom-banner

JPA에서 연관 관계란?

엔티티(Entity)를 작성할 때, 연관관계(Relationship)는 엔티티 간의 일대일, 다대일, 다대다와 같은 관계를 정의하는 것을 말합니다. 이러한 연관관계는 데이터베이스에서 테이블 간의 관계를 나타내는데 사용되며, JPA를 사용하여 객체 지향 애플리케이션에서 이러한 관계를 표현하고 다루는 데 사용됩니다.

연관 관계 정의

단방향, 양방향

  • 테이블은 외래 키 하나로 양 쪽 테이블 조인이 가능합니다.
  • 객체는 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능합니다.
  • 엄밀하게 보자면 양방향 관계는 없고 두 객체가 단방향 참조를 각각 가져서 양방향 관계처럼 사용하는 것입니다.

무조건 양방향 설정이 유리할까?

기본적으로 매핑 관계만을 따져보았을 때 양방향 매핑으로만 한다면 관계가 엔티티가 복잡해집니다. 그렇기 때문에 불필요한 연관관계를 덜고 복잡성을 낮추는 것이 필요합니다. 이를 해결하기 위해 기본적으로 단방향 매칭으로 설정하고 추후에 역방향으로 객체 탐색이 필요하다고 느껴질 때 추가하는 것이 좋습니다.

연관 관계 주인

연관 관계의 주인이란, 두 단방향 관계중 제어의 권한을 가지고 있는 객체입니다. 연관 관계 주인은 두 객체 사이에서의 조회, 저장, 수정, 삭제가 가능하지만, 연관 관계 주인이 아니라면 오직 조회만 가능합니다.

하지만 만약 두 객체가 공동의 연관 관계의 주인이라면 테이블과 매핑을 담당하는 JPA 입장에서 혼란을 주게되므로 연관 관계의 주인은 한 객체만 하는 것이 권장됩니다.

만약 연관 관계의 주인 설정에 어려움을 느낀다면 외래키가 있는 곳을 연관 관계의 주인으로 정하면 됩니다.

연관 관계 종류

일대일(1:1)

일대일 관계는 주 테이블에 외래키를 넣을 수도 있고, 대상 테이블에 외래키를 넣을 수도 있습니다. 즉 외래키를 어디에 두어도 상관은 없지만, 상황에 맞게 선택하는 것이 중요합니다.

일대일(1:1) 단방향

외래 키를 주 테이블이 갖고 있다는 의미로 해석하겠습니다. (Post테이블(주 테이블)에서 외래키(FK)인 Attach 테이블(대상 테이블)의 PK를 갖고 있도록)

게시글(Post)에 첨부파일(Attach)을 반드시 1개만 첨부할 수 있다고 가정합니다.

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

    @Column(name = "TITLE")
    private String title;
    @OneToOne
    @JoinColumn(name = "ATTACH_ID")
    private Attach attach;
//... getter,setter
}
@Entity
public class Attach {
    @Id @GeneratedValue
    @Column(name = "ATTACH_ID")
    private Long id;
    private String name;
//... getter, setter
}

일대일(1:1) 양방향

단순하게 똑같이 @OneToOne 설정하고 mappedBy 설정만 해서 읽기 전용으로 만들어주면 양방향도 간단하게 됩니다.

@Entity
public class Attach {
    @Id @GeneratedValue
    @Column(name = "ATTACH_ID")
    private Long id;
    private String name;

    @OneToOne(mappedBy = "attach")
    private Post post;
//... getter, setter
}

이럴 때는 어차피 양 쪽이 일대일이기 때문에 위에서 정의한 대로 처리하면 됩니다.
하지만 다대일 같은 경우는 다쪽에 외래키를 설정하면 되었지만, 일대일은 외래키를 어디에서 관리하는 게 좋을 것인지에 대한 문제가 있습니다.
테이블은 한 번 생성되면 변경이 어렵지만, 비즈니스는 언제든 바뀔 수 있습니다. 그렇기 때문에 개발자는 각각 객체의 입장에서 생각하고 종합적으로 판단하여, 주 테이블에 외래키를 설정해야합니다.


다대일(N:1)

다대일(N:1) 단방향

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

    @Column(name = "TITLE")
    private String title;

    @ManyToOne
    @JoinColumn(name = "BOARD_ID")
    private Board board;
    //... getter, setter
}

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;
    private String title;
    //... getter, setter
}

다대일에서는 외래키를 게시글인 N에서 관리하는 것이 일반적인 형태입니다.

다대일(N:1) 양방향

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

    @Column(name = "TITLE")
    private String title;

    @ManyToOne
    @JoinColumn(name = "BOARD_ID")
    private Board board;
    //... getter, setter
}

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;
    private String title;

    @OneToMany(mappedBy = "board")
    List<Post> posts = new ArrayList<>();
    //...

@OneToMany 를 추가하고 양방향 매핑을 사용했으니 연관 관계의 주인을 mappedBy 로 지정해줍니다.


일대다(1:N)

일대다(1:N) 단방향

@Entity
public class Post {
    @Id @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;

    @Column(name = "TITLE")
    private String title;
  //... getter, setter
}

@Entity
public class Board {
    @Id @GeneratedValue
    private Long id;
    private String title;

    @OneToMany
    @JoinColumn(name = "POST_ID") //일대다 단방향을 @JoinColumn필수
    List<Post> posts = new ArrayList<>();
    //... getter, setter
}

일대다는 다대일과 달리 연관 관계의 주인을 일로 둔 것입니다. (실무에서는 일대다 단방향은 거의 쓰지 않도록 합니다.)

위의 코드의 문제를 보자면 board를 저장할 때는 Board를 insert하는 쿼리가 나간 후에 post를 update하는 쿼리가 나갑니다.

왜냐하면 board.getPosts().add(post); 부분 때문입니다.

Board 엔티티는 Board 테이블에 매핑되기 때문에 Board 테이블에 직접 지정할 수 있으나, Post 테이블의 FK(BOARD_ID)를 저장할 방법이 없기 때문에 조인 및 업데이트 쿼리를 날려야 하는 문제가 있습니다.

그렇기 때문에 TIP으로 일대다(1:N) 단방향 연관 관계 매핑이 필요한 경우는 그냥 다대일(N:1) 양방향 연관 관계를 매핑해버리는게 추후에 유지보수에 훨씬 수월하기 때문에 이 방식을 채택하는 것을 추천합니다.


다대다(N:M)

다대다 관계는 실무 사용 금지됩니다. 중간 테이블이 숨겨져 있기 때문에 자기도 모르는 복잡한 조인의 쿼리(Query)가 발생하는 경우가 생길 수 있기 때문입니다.

다대다로 자동생성된 중간테이블은 두 객체의 테이블의 외래 키만 저장되기 때문에 문제가 될 확률이 높습니다. JPA를 해보면 중간 테이블에 외래 키 외에 다른 정보가 들어가는 경우가 많기 때문에 다대다를 일대다, 다대일로 풀어서 만드는 것(중간 테이블을 Entity로 만드는 것)이 추후 변경에도 유연하게 대처할 수 있습니다.


참고사이트 : https://jeong-pro.tistory.com/231


피드백 및 개선점은 댓글을 통해 알려주세요😊

profile
배움을 글로 기록하는 개발자가 되겠습니다.
post-custom-banner

0개의 댓글