자주 헷갈리는 JPA 연관 관계를 정리해보자 🔥
N:1
, 일대다1:N
, 일대일1:1
, 다대다N:M
데이터베이스 테이블은 외래 키 하나로 양 쪽 테이블 조인이 가능하고, 둘 다 조회할 수 있다.
그렇기 때문에 데이터베이스는 단방향, 양방향을 구분할 필요가 없다.
하지만 객체는 참조용 필드
가 있는 객체만 다른 객체를 참조하는 것이 가능하다.
두 객체 중 하나의 객체만 참조용 필드를 갖고 참조하면 단방향 관계
,
두 객체 모두 각각 참조용 필드를 가지고 참조하면 양방향 관계
라고 한다.
엄밀하게, 양방향 관계↔️는 두 객체가 단방향 참조를 각각 가져서⬅️➡️ 서로 다른 단방향 관계 2개가 양방향 관계처럼 사용되는 것을 말한다.
객체가 단방향 관계를 가져야 할지, 양방향 관계를 가져야 할지 어떻게 선택할까? 🧐
비즈니스 로직에서 두 객체가 참조가 필요한지 여부를 고민해보면 된다.
Board.getPost()
처럼 참조가 필요하면 Board → Post 단방향 참조post.getBoard()
처럼 참조가 필요하면 Post → Board 단방향 참조이렇게 했을 때, 두 객체가 서로 단방향 참조를 했다면 양방향 연관 관계가 되는 것이다.
객체 입장에서 양방향 매핑을 했을 때 오히려 복잡해질 수 있다.
예를 들어, User 엔티티는 일반적으로 굉장히 많은 엔티티와 연관 관계를 갖는다.
이런 경우 모든 엔티티를 양방향 관계로 설정하게 되면 User 엔티티는 엄청나게 많은 테이블과 연관 관계를 맺게 되어 복잡해질 수 있다.
불필요한 연관관계 매핑으로 인해 복잡성이 증가할 수 있다!
기본적으로는 단방향 매핑을 하고, 역방향으로 객체 탐색이 필요하다고 느낄 때 추가하는 방향으로 매핑을 진행하면 좋다.
엔티티를 단방향으로 매핑하면 참조를 하나만 사용하므로 이 참조로 외래키를 관리하면 된다.
그런데 엔티티를 양방향으로 매핑하면 서로 참조를 하기 때문에, 연관 관계를 관리하는 포인트가 2개로 늘어난다.
양방향 관계에서 객체의 참조는 둘인데 외래키는 하나이다.
그렇기 때문에 JPA에서는 두 연관 관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데,
이를 연관 관계의 주인
이라고 한다.
연관 관계의 주인은 외래키를 관리(등록, 수정, 삭제)할 수 있고, 주인이 아닌 쪽은 읽기만 할 수 있다.
외래키
가 있는 곳으로 설정하자.다중서의 종류에는 아래 4가지가 있다
@ManyToOne
@OneToMany
@OneToOne
@ManyToMany
어노테이션을 작성할 때, @(A)To(B)라고 적을 때,
A 자리를 현재 클래스를 기준으로 적어준다.
1
, 게시글(Post)이 N
이 된다.⚡️ 데이터베이스는 무조건 다(N) 쪽이 외래키를 갖는다.
→ 연관 관계의 주인은 게시글(Post)
!
// 게시글
@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;
}
@ManyToOne
만 추가해준 것을 확인할 수 있다.// 게시글
@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<>();
}
@OneToMany
를 추가하고 mappedBy로 주인이 아닌 것을 표시💥 참고 : 실무에서는 일대다(1:N) 는 거의 쓰지 않는다. 되도록이면 다대일(N:1) 을 사용하자!
일대다(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<>();
}
@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)을 사용하자!
일대일은 거꾸로 해도 일대일이다.
주 테이블에 외래키를 넣을 수도 있고, 대상 테이블에 외래키를 넣을 수도 있다.
테이블 | 단방향 연관 관계 | 양방향 연관 관계 |
---|---|---|
주 테이블 기준 | ⭕️ | ⭕️ |
대상 테이블 기준 | ❌ | ⭕️ |
주 테이블 기준 연관 관계를 살펴보자.
@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;
}
@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;
}
🚫 실무 사용 금지 !!! 🚫
https://jeong-pro.tistory.com/231
https://cornswrold.tistory.com/350
https://cjw-awdsd.tistory.com/47