구리

[JPA] 다양한 연관관계 매핑 본문

JPA

[JPA] 다양한 연관관계 매핑

guriguriguri 2021. 9. 2. 02:21

[다대일 단방향]

- 가장 많이 사용하는 연관관계 

- 참고로 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)

 

 

 

[다양한 연관관계 매핑 실습]

- 배송, 카테고리 추가

- 주문과 배송은 일대일 관계

- 상품과 카테고리는 다대다 관계

 

엔티티 관계도
ERD, 엔티티 상세

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