JPA 연관 관계 정리

짱J·2022년 7월 9일
0

Spring Boot

목록 보기
4/7
post-thumbnail

자주 헷갈리는 JPA 연관 관계를 정리해보자 🔥

연관 관계 정의 규칙

  • 방향 : 단방향, 양방향
  • 연관 관계의 주인
  • 다중성 : 다대일 N:1, 일대다1:N, 일대일1:1, 다대다N:M

단방향, 양방향

데이터베이스 테이블은 외래 키 하나로 양 쪽 테이블 조인이 가능하고, 둘 다 조회할 수 있다.
그렇기 때문에 데이터베이스는 단방향, 양방향을 구분할 필요가 없다.

하지만 객체는 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능하다.
두 객체 중 하나의 객체만 참조용 필드를 갖고 참조하면 단방향 관계 ,
두 객체 모두 각각 참조용 필드를 가지고 참조하면 양방향 관계 라고 한다.

엄밀하게, 양방향 관계↔️는 두 객체가 단방향 참조를 각각 가져서⬅️➡️ 서로 다른 단방향 관계 2개가 양방향 관계처럼 사용되는 것을 말한다.

객체가 단방향 관계를 가져야 할지, 양방향 관계를 가져야 할지 어떻게 선택할까? 🧐
비즈니스 로직에서 두 객체가 참조가 필요한지 여부를 고민해보면 된다.

  • Board.getPost() 처럼 참조가 필요하면 Board → Post 단방향 참조
  • post.getBoard() 처럼 참조가 필요하면 Post → Board 단방향 참조
  • 참조가 굳이 필요 없으면 참조를 안하면 됨

이렇게 했을 때, 두 객체가 서로 단방향 참조를 했다면 양방향 연관 관계가 되는 것이다.

❓ 무조건 양방향 관계를 하면 쉽지 않을까?

객체 입장에서 양방향 매핑을 했을 때 오히려 복잡해질 수 있다.
예를 들어, User 엔티티는 일반적으로 굉장히 많은 엔티티와 연관 관계를 갖는다.

이런 경우 모든 엔티티를 양방향 관계로 설정하게 되면 User 엔티티는 엄청나게 많은 테이블과 연관 관계를 맺게 되어 복잡해질 수 있다.

불필요한 연관관계 매핑으로 인해 복잡성이 증가할 수 있다!

기본적으로는 단방향 매핑을 하고, 역방향으로 객체 탐색이 필요하다고 느낄 때 추가하는 방향으로 매핑을 진행하면 좋다.


연관 관계의 주인

엔티티를 단방향으로 매핑하면 참조를 하나만 사용하므로 이 참조로 외래키를 관리하면 된다.
그런데 엔티티를 양방향으로 매핑하면 서로 참조를 하기 때문에, 연관 관계를 관리하는 포인트가 2개로 늘어난다.

양방향 관계에서 객체의 참조는 둘인데 외래키는 하나이다.
그렇기 때문에 JPA에서는 두 연관 관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데,
이를 연관 관계의 주인 이라고 한다.

연관 관계의 주인은 외래키를 관리(등록, 수정, 삭제)할 수 있고, 주인이 아닌 쪽은 읽기만 할 수 있다.

  • 연관 관계의 주인은 외래키가 있는 곳으로 설정하자.
  • 주인은 mappedBy를 사용하지 않는다.
  • 주인이 아니면 mappedBy를 사용하여 주인을 지정해야 한다.

다중성

다중서의 종류에는 아래 4가지가 있다

  • 다대일 @ManyToOne
  • 일대다 @OneToMany
  • 일대일 @OneToOne
  • 다대다 @ManyToMany

어노테이션을 작성할 때, @(A)To(B)라고 적을 때,
A 자리를 현재 클래스를 기준으로 적어준다.

다대일 (N:1)

  • 하나의 게시판에는 여러 게시글을 작성할 수 있다
  • 하나의 게시글은 하나의 게시판에만 작성할 수 있다.
  • 게시판(Board)이 1 , 게시글(Post)이 N 이 된다.

⚡️ 데이터베이스는 무조건 다(N) 쪽이 외래키를 갖는다.

→ 연관 관계의 주인은 게시글(Post) !

🌊 다대일 (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;
}

// 게시판
@Entity
public class Board {
	@Id
    @GeneratedValue
    private Long id;
    private String title;
}
  • 다(N) 쪽인 Post에 @ManyToOne 만 추가해준 것을 확인할 수 있다.

🌊 다대일 (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;
}

// 게시판
@Entity
public class Board {
	@Id
    @GeneratedValue
    private Long id;
    private String title;
    
    @OneToMany(mappedBy = "board")
    List<Post> posts = new ArrayList<>();
}
  • 일(1) 쪽인 Board에 @OneToMany 를 추가하고 mappedBy로 주인이 아닌 것을 표시
    • 엔티티 이름과 mappedBy에 들어가는 변수가 동일한 것을 볼 수 있다.

일대다 (1:N)

  • 다대일 (N:1) - 연관 관계의 주인이 다(N) 쪽
  • 일대다 (1:N) - 연관 관계의 주인이 일(1) 쪽

💥 참고 : 실무에서는 일대다(1:N) 는 거의 쓰지 않는다. 되도록이면 다대일(N:1) 을 사용하자!
일대다(1:N) 가 왜 문제가 되는지 알아보자.

🌊 일대다 (1:N) 단방향

아래는 일(1)쪽 게시판에서 다(N)쪽 게시글 객체를 조작(생성, 수정, 삭제)하는 방법이다.

// 게시글
@Entity
public class Post {
	@Id
    @GeneratedValue
    @Column(name = "POST_ID")
    private Long id;
    
    @Column(name = "TITLE")
    private String title;    
}

// 게시판
@Entity
public class Board {
	@Id
    @GeneratedValue
    private Long id;
    private String title;
    
    @OneToMany
    @JoinColumn(name = "POST_ID")
    List<Post> posts = new ArrayList<>();
}
  • 단방향이기 때문에 mappedBy가 없어졌다
  • @JoinColumn을 사용하여 조인을 한다.

실제로 사용할 때는 아래와 같이 사용한다.

Post post = new Post();
post.setTitle("가입인사");

entityManager.persist(post); // post 저장

Board board = new Board();
board.setTitle("자유게시판");
board.getPosts().add(post);

entityManager.persist(board); // board 저장

post를 저장할 때는 정상적으로 insert 쿼리가 나간다.
board를 저장할 때는 Board를 insert하는 쿼리가 나간 후 post를 update하는 쿼리가 나간다 >> 🧨 문제 발생 !!! 🧨

board.getPosts().add(post); 때문이다.
Board 엔티티는 Board 테이블에 매핑되기 때문에 Board 테이블에 직접 지정할 수 있으나, Post 테이블의 외래키를 저장할 방법이 없기 때문에 조인 및 업데이트 쿼리를 날려야 한다.

일대다(1:N) 양방향은 공식적으로 존재하지 않으므로 생략한다.

일대다(1:N)는 되도록 사용하지 말고, 다대일(N:1)을 사용하자!


일대일 (1:1)

일대일은 거꾸로 해도 일대일이다.
주 테이블에 외래키를 넣을 수도 있고, 대상 테이블에 외래키를 넣을 수도 있다.

테이블단방향 연관 관계양방향 연관 관계
주 테이블 기준⭕️⭕️
대상 테이블 기준⭕️
  • 00 테이블 기준이라는 것은, 00 테이블이 외래 키를 갖고 있다는 뜻이다.

주 테이블 기준 연관 관계를 살펴보자.

  • 주 테이블 : Post
    • FK로 Attach 테이블의 PK를 가짐
  • 대상 테이블 : Attach

🌊 일대일(1: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;
}

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

🌊 일대일(1: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;
}

@Entity
public class Attach {
	@Id
    @GeneratedValue
    @Column(name = "ATTACH_ID")
    private Long id;
    private String name;
    
    @OneToOne(mappedBy = "attach")
    private Post post;
}
  • mappedBy 설정으로 읽기 전용으로 만든다.

다대다 (N:M)

🚫 실무 사용 금지 !!! 🚫

  • 중간 테이블이 숨겨져 있어 자기도 모르는 복잡한 조인의 쿼리가 발생할 수 있다.

🦖 레퍼런스

https://jeong-pro.tistory.com/231
https://cornswrold.tistory.com/350
https://cjw-awdsd.tistory.com/47

profile
[~2023.04] 블로그 이전했습니다 ㅎㅎ https://leeeeeyeon-dev.tistory.com/

0개의 댓글