[JPA] 6. 연관관계 매핑 심화 (다양한 연관관계)

최진민·2021년 6월 6일
0

JPA

목록 보기
6/11
post-thumbnail
  • 연관관계 매핑시 고려사항 3가지
    • ❗) 다중성 (다대일, 일대다, 일대일, 다대다)
    • ❗) 방향성 (단방향, 양방향)
      • 테이블
        • 외래 키 하나로 양쪽 조인
        • 방향 x
      • 객체
        • 참조용 필드가 있는 쪽으로만 참조
        • 한쪽만 참조 = 단방향
        • 양쪽이 서로 참조 = 양방향
    • ❗) 연관관계 주인
      • 테이블은 외래키 하나로 두 테이블이 연관관계를 맺음
      • 객체는 참조가 두 군데
      • 객체는 둘 중 테이블의 외래키를 관리할 곳을 지정
      • ✨연관관계 주인 : 외래키를 관리하는 참조
      • 주인의 반대편 : 외래키 영향 주지 않고, 단순 조회

다대일 [N:1]

  • 단방향

    • 가장 흔한 연관관계(다대일의 반대? 일대다)
  • 양방향

    • 외래키가 있는 객체가 연관관계 주인
    • 양쪽을 서로 참조
    • 💖TeamList members는 단지 조회를 위한 참조

일대다 [1:N]

  • 단방향

    • 일(1)이 주인

    • 다(N)에 외래키가 들어간다.

    • 객체와 테이블 차이로 반대편 테이블의 외래 키를 관리하는 특이 구조

    • @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용함(중간에 테이블을 하나 추가함)

    • Team

      @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<>();
      }
    • JpaMain

      try {
      
                  Member member = new Member();
                  member.setUsername("member1");
                  em.persist(member);
      
                  Team team = new Team();
                  team.setName("teamA");
      
                  /**
                   * 뭔가 이상?
                   * Team 엔티티에 관해서 수정(.add())을 요청했지만
                   * 결국에 update query는 Member에서 발생한다.
                   * 문제점
                   * 1) 성능상 약간의 손해
                   * 2) (critical) 코드 상, A Entity를 손 봤는데 쿼리를 확인해보니 B Entity가 수정되었다.
                   * 코드가 무수히 많고 서비스가 무수히 많은 실무에서는 헷갈리기 일수.
                   */
                  team.getMembers().add(member);
                  em.persist(team);
      
                  tx.commit();
              }
    • 정리

      • 문제점
        • 엔티티가 관리하는 외래키가 다른 테이블에 있음
        • 연관관계를 위해 추가로 UPDATE QUERY 실행(성능 저하)
      • 💥일대다 단방향보다는 다대일 단방향을 쓰자
  • 양방향

    • 공식적으로 존재 x

    • Member

      @OneToMany
      @JoinColumn(insertable = false, updatable = false)
      private Team team;
      
      //읽기 전용
      //근데 만들지 말자
      //다대일 양방향 사용 지향

일대일 [1:1]

  • 일대일 관계는 반대도 일대일

  • 주 테이블이나 대상 테이블 중에 외래 키 선택 가능

  • 💋외래키에 DB 유니크(UNI) 제약조건 추가

  • 4가지 일대일 상황

    • 1) 주 테이블에 외래 키 단방향

      • 다대일 단방향과 유사

      • 멤버

        @Entity
        public class Member {
        
            @Id
            @GeneratedValue
            @Column(name = "member_id")
            private Long id;
        
            @Column(name = "username")
            private String username;
        
            @OneToOne
            @JoinColumn(name = "locker_id")
            private Locker locker;
        
        }
      • 락커

        @Entity
        public class Locker {
        
            @Id
            @GeneratedValue
            @Column(name="locker_id")
            private Long id;
        
            private String name;
        }
    • 2) 주 테이블에 외래 키 양방향

      • 다대일 양방향과 유사(외래키가 있는 객체가 연관관계 주인)

      • 반대편 → mappedBy

      • 멤버는 단방향 그대로, 락커만 변경

        @Entity
        public class Locker {
        
            @Id
            @GeneratedValue
            @Column(name="locker_id")
            private Long id;
        
            private String name;
        
            @OneToOne(mappedBy = "locker")
            private Member member;
        }
    • 3) 대상 테이블에 외래 키 단방향

      • 💥JPA 지원 X
    • 4) 대상 테이블에 외래 키 양방향

      • 사실 일대일 주 테이블에 외래 키 양방향과 매핑 방법은 같음
  • 예를 들어,, 미래에

    • 한 멤버가 여러 락커를 가질 수 있게 된다면?
      • DB 입장에서는 LOCKER가 MEMBER의 외래 키를 갖는 것이 유리하다.
    • 한 락커를 여러 명이 쓸 수 있게 된다면?
      • DB 입장에서는 MEBMER가 LOCKER의 외래 키를 갖는 것이 유리하다
    • 다시 말해, 💋DB 입장에서는 N쪽이 외래 키를 갖는 것이 유리하다.
  • 정리

    • 주 테이블에 외래키
      • 객체 지향 개발자 선호
      • JPA 매핑 편리
      • 장점 : 주 테이블만 조회해도 대상 테이블에 데이터 유무 조회 가능
      • 단점 : 값이 없으면 외래 키에 null 허용
    • 대상 테이블에 외래키
      • 데이터베이스 개발자 선호
      • 장점 : 주 테이블과 대상 테이블의 연관관계 변경 시, 테이블 구조 유지
      • 단점 : 💥프록시 기능의 한계(지연 로딩으로 설정해도 즉시 로딩된다.)

다대다 [N:M]

  • 실무에서 웬만하면 안 쓴다.

  • 관계형 DB는 정규화된 테이블 2개로 다대다 관계를 정의할 수 없다.

    • 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야함 (테이블)

    • 객체는 가능

  • 구성

    • @ManyToMany

    • @JoinTable

    • 단방향, 양방향 가능

      • 단방향

        • 멤버

          @Entity
          public class Member {
          
              @Id
              @GeneratedValue
              @Column(name = "member_id")
              private Long id;
          
              @Column(name = "username")
              private String username;
          
              @ManyToMany
              @JoinTable(name = "memeber_product")
              private List<Product> products = new ArrayList<>();
          }
        • 생산품

          @Entity
          public class Product {
          
              @Id
              @GeneratedValue
              @Column(name = "product_id")
              private Long id;
          
              private String name;
          
          }
      • 양방향

        • 생상품 (멤버는 그대로)

          @Entity
          public class Product {
          
              @Id
              @GeneratedValue
              @Column(name = "product_id")
              private Long id;
          
              private String name;
          
              @ManyToMany(mappedBy = "products")
              private List<Member> members = new ArrayList<>();
          }
  • 한계

    • 연결 테이블이 단순히 연결만 하는게 아니다.

    • 주문시간, 수량 같은 다른 데이터가 필요할 수 있다.

  • 한계 극복

    • 연결 테이블을 엔티티 추가

    • @ManyToMany@OneToMany + @ManyToOne

    • 멤버

      @Entity
      public class Member {
      
          @Id
          @GeneratedValue
          @Column(name = "member_id")
          private Long id;
      
          @Column(name = "username")
          private String username;
          
          @OneToMany(mappedBy = "member")
          private List<MemberProduct> memberProducts = new ArrayList<>();
      
      }
    • 멤버프로덕트(오더)

      @Entity
      @Table(name = "order")
      public class MemberProduct {
      
          @Id
          @GeneratedValue
          @Column(name = "order_id")
          private Long id;
      
          @ManyToOne
          @JoinColumn(name = "member_id")
          private Member member;
      
          @ManyToOne
          @JoinColumn(name = "product_id")
          private Product product;
      
          private int count;
          private int price;
      
          @Column(name="orderdate")
          private LocalDateTime orderDateTime;
      }
    • 프로덕트

      package hellojpa;
      
      import javax.persistence.*;
      import java.util.ArrayList;
      import java.util.List;
      
      @Entity
      public class Product {
      
          @Id
          @GeneratedValue
          @Column(name = "product_id")
          private Long id;
      
          private String name;
      
          @OneToMany(mappedBy = "product")
          private List<MemberProduct> memberProducts = new ArrayList<>();
      }

예제 3) 다양한 연관관계 매핑

  • 주문과 배송은 1:1(@OneToOne)

  • 상품과 카테고리는 N:M(@ManyToMany)

  • ERD

  • 엔티티 상세

  • 회원

    @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<>();
    }
  • 주문

    @Entity
    @Table(name = "orders")
    public class Order {
    
        @Id
        @GeneratedValue
        @Column(name = "order_id")
        private Long id;
    
        private LocalDateTime orderDate;
    
        @Enumerated(EnumType.STRING)
        private OrderStatus status;
    
        @ManyToOne
        @JoinColumn(name = "member_id")
        private Member member;
    
        @OneToOne
        @JoinColumn(name = "delivery_id")
        private Delivery delivery;
    
        @OneToMany(mappedBy = "order")
        private List<OrderItem> orderItems = new ArrayList<>();
    
    }
  • 배송

    @Entity
    public class Delivery {
    
        @Id
        @GeneratedValue
        @Column(name = "delivery_id")
        private Long id;
    
        private String city;
        private String street;
        private String zipcode;
    
        @Enumerated(EnumType.STRING)
        private DeliveryStaus deliveryStaus;
    
        @OneToOne(mappedBy = "delivery")
        private Order order;
    }
  • 주문 상품

    @Entity
    @Table(name = "order_item")
    public class OrderItem {
    
        @Id
        @GeneratedValue
        @Column(name = "order_item_id")
        private Long id;
    
        private int orderPrice;
        private int count;
    
        @ManyToOne
        @JoinColumn(name = "order_id")
        private Order order;
    
        @ManyToOne
        @JoinColumn(name = "item_id")
        private Item item;
        
    }
  • 상품

    @Entity
    public class Item {
    
        @Id
        @GeneratedValue
        @Column(name = "item_id")
        private Long id;
    
        private String name;
        private int price;
        private int stockQuantity;
    
        @OneToMany(mappedBy = "item")
        private List<CategoryItem> categoryItems = new ArrayList<>();
    }
  • 카테고리

    @Entity
    public class Category {
    
        @Id
        @GeneratedValue
        @Column(name = "category_id")
        private Long id;
    
        private String name;
    
        @ManyToOne
        @JoinColumn(name = "parent_id")
        private Category parent;
    
        @OneToMany(mappedBy = "parent")
        private List<Category> child = new ArrayList<>();
    
        @OneToMany(mappedBy = "category")
        private List<CategoryItem> categoryItems = new ArrayList<>();
    }
  • 카테고리_아이템

    @Entity
    @Table(name = "category_item")
    public class CategoryItem {
    
        @Id
        @GeneratedValue
        @Column(name = "category_item_id")
        private Long id;
    
        @ManyToOne
        @JoinColumn(name = "item_id")
        private Item item;
    
        @ManyToOne
        @JoinColumn(name = "category_id")
        private Category category;
    }

  • N:M 관계는 1:N, N:1로

    • 실무에서는 단순하지 않은 중간 테이블
    • @ManyToMany는 제약
      • 필드 추가 x
      • 엔티티 테이블 불일치
  • 정리

    • JoinColumn : 외래키 매핑시 사용

    • @ManyToOne : 다대일 관계 매핑

      • targetEntity는 몰라도 된다.
    • @OneToMany : 일대다 관계 매핑

      • 마찬가지로 몰라도 된다.
profile
열심히 해보자9999

0개의 댓글