반응형
본 포스트는 사내 스터디로 진행되는 자바 ORM 표준 JPA 프로그래밍 관련 정리입니다
인프런 강의도 있습니다! 짱짱
https://www.inflearn.com/course/ORM-JPA-Basic
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔
www.inflearn.com
6장. 다양한 연관관계 매핑
- 엔티티의 연관관계를 매핑할 때는 다음 3가지 고려사항이 있다
- 다중성
- 다대일(@ManyToOne)
- 일대다(@OneToMany)
- 일대일(@OneToOne)
- 다대다(@ManyToMany)
- 단방향, 양방향
- 테이블은 외래 키(FK) 하나로 조인을 사용해서 양방향으로 쿼리가 가능하므로 사실상 방향이라는 개념이 없다
- 객체는 참조용 필드를 가지고 있는 객체만 연관된 객체를 조회할 수 있다
- 객체 관계에서 한 쪽만 참조하는 것을 단방향, 양쪽에서 서로 참조하는 것을 양방향이라고 한다
- 연관관계의 주인
- JPA는 두 객체 연관관계 중 하나를 정해서 데이터베이스 외래 키(FK)를 관리하는데 이것을 연관관계의 주인이라 한다
- 외래 키를 가진 테이블과 매핑한 엔티티(일반적으로 다쪽에 해당하는)가 외래 키를 관리하는 게 효율적이므로 보통 이곳을 연관관계의 주인으로 선택
- 주인이 아닌 방향은 외래 키를 변경할 수 없고 읽기만 가능하다
- 다중성
6-1. 다대일(@ManyToOne)
- 데이터베이스 테이블의 일대다, 다대일 관계에서 외래 키는 항상 다쪽에 있다
- 따라서 연관관계의 주인은 항상 다쪽이다 ( 외래 키가 있는 곳으로 설정해야 효율적이다 )
- 다대일 단방향(N:1)
// N:1 다대일 단방향 @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "TEAM_ID") private Team team; ... }
// 다대일 @Entity public class Team { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; ... }
- Team 객체와 @ManyToOne의 관계를 맺고 있는 것을 확인할 수 있다
- Team(일) 대 Member(다) 관계이며, 다쪽인 Member에 @JoinColumn(외래 키)가 설정되어 있다
- 단방향이기 때문에 Member에서 Team쪽의 참조만 가지고 있고 Team쪽에서는 다른 참조관계를 갖고있지 않다
- 다대일 양방향(N:1, 1:N)
// 다대일 @Entity public class Team { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(mappedBy = "team") private List<Member> members = new ArrayList<>(); ... public void addMember(Member member) { this.members.add(member); if (member.getTeam() != this) { member.setTeam(this); } } ... }
-
// N:1 다대일 단방향 @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne @JoinColumn(name = "TEAM_ID") private Team team; ... public void setTeam(Team team) { this.team = team; if (false == team.getMembers().contains(this)) { team.getMembers().add(this); } } ... }
- 기존에 아무 참조관계도 없던 Team 클래스에 @OneToMany(일대다) 관계가 설정되었습니다
- 양방향 관계에서는 연관관계 주인이 아닌 곳에서는 mappedBy 속성을 통해 반대편의 연관관계 주인을 나타내줘야합니다
- 양방향 관계에서는 객체 그래프 탐색을 통해 서로를 조회할 수 있으므로 무한 루프에 빠질 수 있습니다
- 둘 중 한 곳에서만 셋팅되는 것을 막기위해 한 쪽에서 Team과 Member 둘 다 참조관계를 셋팅해 주고 있고, 이를 편의 메서드라고 부릅니다
- Team 객체의 addMember 메서드, Member 객체의 setTeam 메서드 설정 중 예외처리 하는 부분이 무한 루프를 방지하기 위한 구문입니다
- Lombok의 @Data, @ToString 등의 자동으로 Generate 되는 코드들도 조심해야합니다 ( 서로 ToString 메서드를 호출하다가 무한 루프에 빠짐 )
6-2. 일대다(@OneToMany)
- 일다다 관계는 위에서 설명했던 다대일(@ManyToOne)의 반대 방향입니다
- 사용 가능 컬렉션 타입
- Collection
- List
- Set
- Map
- 일대다 단방향(1:N)
// 일대다 단방향 @Entity public class Team { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany @JoinColumn(name = "TEAM_ID") private List<Member> members = new ArrayList<>(); ... }
- 일대다 단방향 관계에서도 역시 외래 키를 관리하는 연관관계의 주인을 설정해야하는데, 코드에서 보시다시피 외래 키를 갖는 테이블(Member)이 아니라 참조하는 곳에서 @JoinColumn을 관리하고 있습니다
- 단방향에서는 Member Entity가 외래 키를 매핑할 수 있는 참조 필드가 없기 때문에 반대쪽인 Team Entity에서 관리하는 것을 확인 할 수 있습니다
- 만약 일대다 단방향 관계에서 @JoinColumn을 명시하지 않으면, JPA는 연결 테이블을 중간에 두고 연관관계를 관리하는 Join Table 전략을 기본으로 사용해서 매핑합니다
- 일대다 단방향 매핑의 단점
- 매핑한 객체가 관리하는 외래 키가 다른 테이블에 존재
- 즉 매핑된 테이블에 외래 키가 있으면 엔티티의 저장과 연관관계 처리시 INSERT 쿼리 한 번에 데이터 삽입을 할 수 있지만, 다른 테이블에 외래 키가 있으면 연관관계 처리를 위한 UPDATE 쿼리가 추가적으로 실행된다
- 따라서 일대다 단방향보다는 다대일 양방향 매핑을 권장한다
- 개인적인 생각으로 양방향 참조관계는 또 다른 문제를 야기할 수 있다..
- 조용호님의 객체지향 관련 영상을 참고, https://www.youtube.com/watch?v=dJ5C4qRqAgA
6-3. 일대일(1:1)
- 일대일 관계는 양쪽이 서로 하나의 관계만 갖는다
- 일대일 관계는 반대도 일대일 관계
- 일대일 관계는 양쪽 모두에서 외래 키를 가질 수 있다
- 주 테이블이 외래 키를 갖는 경우, 주 테이블에 @JoinColumn을 이용해 테이블에 외래키 명시
- 대상 테이블이 외래 키를 갖는 경우, 주 테이블에 @OneToOne(mappedBy="")를 설정
- 프록시를 사용할 때 외래 키를 직접 관리하지 않는 일대일 관계는 지연 로딩(Lazy)으로 설정해도 즉시 로딩(Eagar)된다
- 정확히 말하면 JPA는 일대일 관계에서 무조건적인 Lazy loading을 지원하지 않는다
- JPA는 객체의 참조가 프록시 기반으로 동작하는데, 연관관계가 있는 객체는 참조를 할때 기본적으로 null이 아닌 프록시 객체를 반환한다
- 하지만 @OneToOne 관계에서 null이 허용되는 경우는 프록시 형태로 null 객체를 반환할 수 없기 때문에 지연 로딩을 할 수 없다
- @OneToMany는 배열 형태로 원소가 없는 컬렉션을 지원하기 때문에 가능
- 지연로딩 발동 조건
- nullable이 허용되지 않는 1:1 관계, 참조 객체가 optional=false로 지정할 수 있는 관계여야한다
- 양방향이 아닌 단방향 1:1 관계여야한다
- @PrimaryKeyJoin은 허용되지 않는다. 부모,자식간 엔티티의 조인컬럼이 모두 PK인 경우
6-4. 다대다(N:M)
- 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다 ( PK를 잡을 수 없음 )
- 따라서 1:N, N:1로 풀어내야하며, 중간에 연결 테이블(정션 테이블, 매핑 테이블)을 추가적으로 설계해야한다
- 하지만 객체는 관계형 데이터베이스와 다르게 객체 2개로 다대다 관계를 만들 수 있다
- 다대다 단방향
// 다대다: 단방향 @Entity public class Member { @Id @Column(name = "MEMBER_ID") private String id; private String username; @ManyToMany @JoinTable(name = "MEMBER_PRODUCT", joinColumns = @JoinColumn(name = "MEMBER_ID"), inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID")) private List<Product> products = new ArrayList<>(); ... }
- Member 엔티티와 Product 엔티티를 @ManyToMany, 다대다로 연관관계를 설정했다
- N:M 관계를 풀기위해서는 정션테이블, 즉 중간에 매핑테이블이 필요하기 때문에 @JoinTable 설정을 통해 따로 엔티티를 만들지 않고 풀어낼 수 있다
- @JoinTable
- name: 연결 테이블을 지정
- joinColumns: 현재 방향에서 매핑한 조인 컬럼 정보 지정
- inverseJoinColumns: 반대 방향에서 매핑한 조인 컬럼 정보 지정
- 따라서 Member 엔티티에 Product 연관관계를 추가 후 persist를 호출하면 아래와 같은 쿼리가 발생한다
# @ManyToMany INSERT INTO PRODUCT ... INSERT INTO MEMBER ... INSERT INTO MEMBER_PRODUCT ...
- @ManyToMany 조회
- Code
// find public void find() { Member member = entityManager.find(Member.class, "member1"); List<Product> products = member.getProducts(); // 객체 그래프 탐색 for (Product product : products) { System.out.println("product.name = " + product.getName()); } }
- Lazy 로딩인 member.getProducts() 호출시 발생 쿼리
# @ManyToMany SELECT * FROM MEMBER_PRODUCT MP INNER JOIN PRODUCT P ON MP.PRODUCT_ID = P.PRODUCT_ID WHERE MP.MEMBER_ID=?
- Code
- 다대다 양방향
- 기존의 양방향처럼 연관관계의 주인을 설정하여 @JoinTable 설정을 하고, 연관관계의 주인이 아닌 곳에서는 mappedBy를 통해 연관관계 주인을 알려주면 된다
- Code
// @ManyToMany 반대방향 @Entity public class Product { @Id private String id; @ManyToMany(mappedBy = "products") private List<Member> members; ... }
- mappedBy를 통해 Member 엔티티의 "products" 필드 속성이 연관관계 주인임을 알려줬다
- @ManyToMany의 한계
- @ManyToMany 설정은 연결 테이블(정션, 매핑)을 자동으로 처리해주므로, 코드가 단순해지고 편리하지만 한계가 존재한다
- 문제점
- 실무를 하다보면 연결 테이블에는 단순히 매핑된 엔티티들의 키값만 들어가지 않는다 ( @ManyToMany, @JoinTable로는 컬럼 추가가 불가능하다.. )
- 따라서 연결테이블을 관리하는 엔티티를 직접 설계하는 것이 효율적이다
- 참고: 식별 관계 vs 비식별 관계
- 식별 관계: 부모 테이블의 기본 키를 받아서 자식 테이블의 기본 키 + 외래 키로 사용하는 것을 식별 관계라고 한다
- 비식별 관계: 단순히 외래 키로만 사용하는 것을 비식별 관계라고 한다
728x90
반응형
'Study > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
5장. 연관관계 매핑 기초 (0) | 2021.09.04 |
---|---|
4장. 엔티티 매핑 (0) | 2021.08.31 |
3장. 영속성 관리 (0) | 2021.08.22 |
1장. JPA 소개 (0) | 2021.08.09 |