일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- React.memo
- TypeScript
- Compound Component
- CS
- 프로세스
- Sparkplug
- linux 배포판
- prettier-plugin-tailwindcss
- JavaScript
- Event Loop
- Dockerfile
- Microtask Queue
- 명시적 타입 변환
- type assertion
- useCallback
- prettier
- useEffect
- Custom Hook
- 좋은 PR
- AJIT
- useMemo
- docker
- 암묵적 타입 변환
- Headless 컴포넌트
- 주니어개발자
- useLayoutEffect
- Render Queue
- react
- 타입 단언
- task queue
- Today
- Total
구리
[JPA] 다양한 연관관계 매핑 본문
[다대일 단방향]
- 가장 많이 사용하는 연관관계
- 참고로 DB에서 다대일 관계일 때 다(많은)쪽 테이블에 외래키를 넣어주는 것이 올바른 설계
package hellojpa;
import javax.persistence.*;
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private Integer age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
package hellojpa;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
}
[다대일 양방향]
- 외래 키가 있는 쪽이 연관관계의 주인
- 양쪽을 서로 참조하도록 개발
- 주인의 반대쪽에선 참조 변수 설정시 mappedBy로 연관관계 주인 설정 잊지 않기
package hellojpa;
import javax.persistence.*;
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
private Integer age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
package hellojpa;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
private String name;
}
[일대다 단방향]
- 일대다(1:N)에서 일(1)이 연관관계의 주인
- 객체와 테이블 차이 때문에 반대편 테이블의 외래 키를 관리하는 특이한 구조
- @JoinColumn을 꼭 사용해야 함 (그렇지 않으면 조인 테이블 방식을 사용함 = 중간에 테이블 하나 추가됨)
결론적으로 엔티티가 관리하는 외래 키가 다른 테이블에 있기에 연관관계 관리를 위해 추가로 UPDATE SQ이 실행됩니다.
따라서 일대다 단방향 매핑보다는 다대일 양방향 매핑 사용을 권장합니다.
package hellojpa;
import javax.persistence.*;
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
}
package hellojpa;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<Member>();
getter, setter...
}
* 실습
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args){
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member();
member.setName("member1");
em.persist(member);
Team team = new Team();
team.setName("teamA");
team.getMembers().add(member);
em.persist(team);
tx.commit();
}catch (Exception e){
tx.rollback();
e.printStackTrace();
}finally {
em.close();
emf.close();
}
}
}
결과
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(USERNAME, id)
values
(?, ?)
Hibernate:
/* insert hellojpa.Team
*/ insert
into
Team
(name, TEAM_ID)
values
(?, ?)
Hibernate:
/* create one-to-many row hellojpa.Team.members */ update
Member
set
TEAM_ID=?
where
id=?
위 실습 코드를 실행하면 team, member insert 쿼리문이 실행된 후 update member 쿼리문이 실행된 것을 볼 수 있습니다.
연관관계가 일대다로 Team 엔티티에서 설정했으므로 Member 테이블의 team_id를 수정하기 위해 update 쿼리문이 실행될 수 밖에 없습니다. 실무에선 큰 성능 차이가 있는 건 아니지만 직관적으로 연관관계를 알아보기도 어렵고 해서 권장하지 않습니다.
[일대다 양방향]
- 이런 매핑은 공식적으로 존재하진 않음
- @JoinColumn(insertable=false, updatable=false)로 읽기 전용 필드를 사용하여 양방향처럼 사용
- 결론적으론 설계는 단순하고 직관적으로 하는 것이 좋기에 다대일 양방향 매핑이 가장 좋음
package hellojpa;
import javax.persistence.*;
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID",insertable = false, updatable = false) // 읽기 전용
private Team team;
getter, setter...
}
package hellojpa;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<Member>();
getter, setter ...
}
[일대일 단방향 (주테이블)]
- 일대일 관계는 그 반대로 일대일
- 주 테이블 혹은 대상 테이블 중에 외래 키 선택 가능 (참고로 주테이블은 비즈니스 관점에서 조회를 더 많이 하는 쪽)
1) 주 테이블에 외래 키
2) 대상 테이블에 외래 키
- 외래 키에 DB 유니크(UNI) 제약조건 추가 권장
* 예시
비즈니스 상황 : 회원(Member)은 사물함(Locker)을 1개만 가질 수 있으며, 각 사물함도 1개의 회원만을 가질 수 있음
package hellojpa;
import javax.persistence.*;
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
@Column(name = "USERNAME")
private String name;
getter,setter ...
}
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Locker {
@Id
@GeneratedValue
private Long id;
private String name;
getter, setter ...
}
[일대일 양방향 (주테이블)]
- 다대일 양방향 매핑 처럼 외래 키가 있는 곳이 연관관계의 주인
- 반대편은 mapperBy 적용
package hellojpa;
import javax.persistence.*;
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
@Column(name = "USERNAME")
private String name;
getter,setter ...
}
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
@Entity
public class Locker {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
[일대일 단방향 (대상테이블)]
- 대상 테이블 기준으로 일대일 단방향 관계는 JPA 지원하지 않음
- 하지만 양방향 관계는 지원
[일대일 양방향 (대상테이블)]
[일대일 정리]
- 주 테이블에 외래 키
- 주 객체가 대상 객체의 참조를 가지는 것 처럼 주 테이블에 외래 키를 두고 대상 테이블을 찾음
- 객체지향 개발자 선호
- JPA 매핑 편리
- 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능
- 단점 : 값이 없으면 외래 키에 null 허용
(만약에 Member 테이블에 locker_id가 있을 경우 해당 멤버는 사물함이 없다면 locker_id가 없으므로 null을 넣어야함)
- 대상 테이블에 외래 키
- 대상 테이블에 외래 키가 존재
- 전통적 DB 개발자 선호
- 장점 : 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경시 테이블 구조 유지 가능
(LOCKER 테이블이 member_id를 가지고 있을 때 회원 : 사물함 이 일대다 관계로 변경되어도 LOCKER 테이블이 다(많은)이기에 테이블 구조 유지가 가능함) - 단점 : 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩됨
(대상 테이블 외래 키 양방향일때, Member 조회시 locker의 유무를 알고 싶다면 MEMBER 테이블에는 locker에 대한 정보가 없기에 LOCKER 테이블을 조회할 수 밖에 없는 상황)
* @JoinColumn
외래 키를 매핑할 때 사용
- name : 매핑할 외래 키의 이름
- referencedColumnName : 외래 키가 참조하는 대상 테이블의 컬럼명 (참조하는 대상 테이블의 컬럼이 PK가 아닌 경우)
(referencedColumnName 를 생략하면 대상 테이블의 PK로 자동지정되기에 pk가 아닌 다른 컬럼에 직접 지정할 수도 있지만 정규화 관점에서는 권장 X)
[다양한 연관관계 매핑 실습]
- 배송, 카테고리 추가
- 주문과 배송은 일대일 관계
- 상품과 카테고리는 다대다 관계
package jpabook.jpashop.domain;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String name;
private String city;
private String street;
private String zipcode;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<Order>();
getter, setter ...
}
package jpabook.jpashop.domain;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "ORDERS")
public class Order {
@Id
@GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<OrderItem>();
@OneToOne
@JoinColumn(name = "DELIVERY_ID")
private Delivery delivery;
private LocalDateTime orderDate;
@Enumerated(EnumType.STRING)
private OrderStatus status;
getter, setter...
}
package jpabook.jpashop.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
@Entity
public class Delivery {
@Id @GeneratedValue
private Long id;
@OneToOne(mappedBy = "delivery")
private Order order;
private String city;
private String street;
private String zipcode;
private DeliveryStatus status;
getter, setter ...
}
package jpabook.jpashop.domain;
import javax.persistence.*;
@Entity
public class OrderItem {
@Id
@GeneratedValue
@Column(name = "ORDER_ITEM_ID")
private Long id;
@ManyToOne
@JoinColumn(name = "ORDER_ID")
private Order order;
@ManyToOne
@JoinColumn(name = "ITEM_ID")
private Item item;
private int orderPrice;
private int count;
}
package jpabook.jpashop.domain;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Item {
@Id
@GeneratedValue
@Column(name = "ITEM_ID")
private Long id;
private String name;
private int price;
private int stockQuantity;
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<Category>();
}
package jpabook.jpashop.domain;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Category {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "CATEGORY_ITEM",
joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID")
)
private List<Item> items = new ArrayList<Item>();
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Category parent;
@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<Category>();
}
출처 : 자바 ORM 표준 JPA 프로그래밍 - 김영한
'JPA' 카테고리의 다른 글
[JPA] 연관관계 매핑 (0) | 2021.09.01 |
---|---|
[JPA] JPA 기초 & 영속성 컨텍스트 (0) | 2021.08.30 |