반응형
목차
- 연관관계 매핑 시 고려사항 3가지
- 다대일
- 일대다
- 일대일
- 다대다
- 예제
연관관계 매핑 시 고려사항
다중성
- 다대일 : @ManyToOne
- 일대다 : @OneToMany
- 일대일 : @OneToOne
- 다대다 : @ManyToMany
단방향, 양방향
- 테이블
- 외래 키 하나로 양쪽 조인 가능 즉, 방향의 개념이 없다.
- 객체
- 참조용 필드가 있는 쪽으로만 참조 가능 (단방향)
- 양방향을 만드려면 단방향을 2개 만드는 방식
연관관계 주인
- 외래키를 관리하는 곳을 연관관계 주인으로 관리하자. 주인의 반대편은 단순 조회만 가능하도록 설계하는 것이 바람직하다.
다대일 [N:1]
다대일 단방향
- 가장 많이 사용하는 연관관계이다. 자세한 내용은 이전 포스팅을 참고하자.
다대일 양방향
- 외래 키가 있는 쪽이 연관관계의 주인이고 양쪽을 서로 참조하도록 개발한다. 단방향만으로 설계를 마친 후 별도의 비즈니스 로직이 필요하거나 하인 쪽에서 자주 조회할일이 생길 때 양방향 연관관계를 주로 사용한다.
일대다 [1:N]
- 해당 모델은 권장하지 않지만, 스펙 상 존재하기 때문에 간단하게 정리하자.
- 연관관계의 주인은 항상 외래키가 없는 1쪽이 된다.
일대다 단방향
회원 엔티티
package hellojpa;
import javax.persistence.*;
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
/* @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
@Column(name = "TEAM_ID")
private Long id;
private String name;
// @OneToMany(mappedBy = "team") // Member 클래스의 team 변수와 연결
// private List<Member> members = new ArrayList<>();
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
public List<Member> getMembers() {
return members;
}
}
JpaMain
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
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();
} finally {
em.close();
}
emf.close();
}
}
Team이 연관관계의 주인으로 외래키의 값을 수정할 때 결국 하인 테이블의 값을 변경해주기 위한 별도의 쿼리문이 추가적으로 발생한다. 즉, 성능 상으로 단점이 있고, 쿼리문에 대한 가독성이 떨어져서 운영하기가 어려워진다. 즉, 다대일 단방향으로 사용하다가, 필요한 경우 양방향으로 사용하도록 설계하자.
일대다 양방향
package hellojpa;
import javax.persistence.*;
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(name="TEAM_ID",insertable = false, updatable = false)
private Team team;
/* @ManyToOne // 해당 클래스의 관점에서 지정
@JoinColumn(name = "TEAM_ID")
private Team team;*/
}
- Member(다) 입장에서 Team을 읽기 용으로 사용해야한다. 하지만, 이 부분은 연관관계의 주인으로 설정할 때와 같다. 그렇기 때문에
insertable, updatable = false
설정을 해줘야 한다. - 이런 매핑은 공식적으로 존재하지 않는다. 다대일 양방향을 사용하자.
일대일 [1:1]
일대일 관계
- 주 테이블이나 대상 테이블 중에 외래키 선택 가능 (주 테이블 외래키를 선택 권장)
- 외래 키에 데이터베이스 유니크 제약조건이 추가된 것과 동일하다.
일대일 단반향
회원 엔티티
package hellojpa;
import javax.persistence.*;
import java.util.concurrent.locks.Lock;
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(insertable = false, updatable = false)
private Team team;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
/* @ManyToOne // 해당 클래스의 관점에서 지정
@JoinColumn(name = "TEAM_ID")
private Team team;*/
}
- 다대일(@ManyToOne) 단방향 매핑과 매우 유사하며 애노테이션만 @OneToOne을 사용한다.
대상 테이블에 외래 키 단방향 즉, 일대다처럼 구현해야될 때는 어떻게 될까?
- 대상 테이블에 외래 키가 있는 경우 단방향 관계는 JPA에서 지원하지 않는다.
- 양방향은 주 테이블의 양방향과 매핑방법이 같으므로 지원한다.
일대일 양방향
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;
}
- 양방향도 다대일 양방향과 마찬가지로
mappedBy
를 통해 객체를 지정해준다.
일대일 정리
주 테이블에 외래 키
- JPA 매핑이 편리하여 주 테이블만 조회해도 대상 테이블에 데이터 확인 가능
- 단, 값이 없으면 외래 키에 null을 허용한다.
대상 테이블에 외래 키
- 일대일 테이블에서 일대다로 관계가 변경된다면 테이블 구조 유지에 용이하다.
- 프록시 기능의 한계로, 지연 로딩으로 설정해도 항상 즉시 로딩된다. JPA는 프록시를 사용할 때 데이터에 값이 있는지 없는지 여부를 알아야한다. 하지만, 대상 테이블에 외래 키가 있으면 대상 테이블을 찾아서 조회해서 동작하게된다. 즉, 어차피 쿼리를 사용해야되기 때문에 지연로딩을 사용하지 않게 된다.
다대다
관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없다. 무조건 일대다, 다대일 관계로 풀어야한다.
객체는 컬렉션을 사용해서 객체 2개로 다대다 관계가 가능(@ManyToMany
)하지만 실무에서 사용하지 않는다.
사용 예시
package hellojpa;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(insertable = false, updatable = false)
private Team team;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
/* @ManyToOne // 해당 클래스의 관점에서 지정
@JoinColumn(name = "TEAM_ID")
private Team team;*/
}
@ManyToMany
와@JoinTable
을 사용해서 만들 수 있다.- 양방향으로 만들 경우 반대편에서
mappedBy
를 사용해야한다.
편리해보이지만 실무에서 쓰지 않는 이유가 있다. 예를 들어서 주문시간, 수량과 같은 데이터가 추가되면 다대다 관계 사이에 만들어진 테이블에 넣을 수가 없다. 쿼리 자체도 중간테이블이 숨겨져있기 때문에 보기가 어렵다.
다대다 관계는 일대다, 다대일로 풀어내자.
다대다 풀어내기
상품 엔티티
package hellojpa;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import java.util.ArrayList;
import java.util.List;
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "product")
private List<MemberProduct> memberProducts = new ArrayList<>();
}
회원 엔티티
package hellojpa;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String name;
@ManyToOne
@JoinColumn(insertable = false, updatable = false)
private Team team;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
@OneToMany(mappedBy = "member")
private List<Product> products = new ArrayList<>();
/* @ManyToOne // 해당 클래스의 관점에서 지정
@JoinColumn(name = "TEAM_ID")
private Team team;*/
}
회원상품 엔티티
package hellojpa;
import javax.persistence.*;
@Entity
public class MemberProduct {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member member;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product product;
}
- 중간 테이블인 회원상품 테이블은 양쪽 모두에서 연관관계의 주인이 된다.
예제
지난 포스팅에 이어 예제를 진행해보자. 다대다 관계는 사용하지 않지만, 예제를 통해 확인해보자.
배송, 카테고리 엔티티 추가
- 주문과 배송은 일대일 관계, 상품과 카테고리는 다대다 관계이다.
ERD
엔티티 상세
카테고리 엔티티
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;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Category parent;
@OneToMany(mappedBy = "parent") // 양방향 관계를 셀프로 설정
private List<Category> child = new ArrayList<>();
@ManyToMany
@JoinTable(name = "CATRGORY_ITEM",
joinColumns = @JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = @JoinColumn(name = "ITEM_ID")
)
private List<Item> items = new ArrayList<>();
}
상품 엔티티
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<>();
}
배송 엔티티
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;
private String city;
private String street;
private String zipcode;
private DeliveryStatus status;
@OneToOne(mappedBy = "delivery")
private Order order;
}
애노테이션 정리
@JoinColumn
- 외래키를 매핑할 때 사용한다.
@ManyToOne
- 다대일 관계에서는 무조건 연관관계의 주인이 되어야하기 때문에
mappedBy
속성이 없다.
@OneToMany
- 다대일 관계 매핑에서 하인이 되는 쪽에서 사용하는 애노테이션이다.
본 포스팅은 인프런 - 김영한님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편'을 참고하였습니다.
반응형