WEB/JPA

김영한 (ORM 표준 JPA 프로그래밍 6) 다양한 연관관계 매핑

Tony Lim 2021. 3. 8. 15:37
728x90

일대다 [1:N]

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<>();


    public Long getId()
    {
        return id;
    }

    public void setId(Long id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public List<Member> getMembers()
    {
        return members;
    }

    public void setMembers(List<Member> members)
    {
        this.members = members;
    }
}

이번엔 Team 이 주인이되어 @JoinColumn을 달게 되었는데 이렇게 된다면 예시코드에서 문제아닌 문제가 생긴다.

  try
        {
            Member member = new Member();
            member.setUsername("member1");

            em.persist(member);

            Team team = new Team();
            team.setName("teamA");
            team.getMembers().add(member);

            em.persist(team);


            tx.commit();
        }

team.getMebers().add(member) 전까지는 insert 쿼리 2개가 날라가는 것은 동일한다. 

Hibernate: 
    /* create one-to-many row hellojpa.Team.members */ update
        Member 
    set
        TEAM_ID=? 
    where
        MEMBER_ID=?

update 쿼리도 날라간다. 이는 팀 테이블에서만 해결할 수 있는 방도가 아니기 떄문이다.

그렇게 많은 손해가 아니지만 실무에서는 여러 테이블들이 얽히고 섥히는데 분명 난 team을 건드렸는데 왜 Member 테이블에서 udpate 가 나가지? 이런 고민자체가 스트레스이기에 김영한님은 다대일 을 주로 사용한다 하셨다.

1. 엔티티가 관리하는 외래키가 다른 테이블에 있음 // 위의 경우 team Entitiy에서 맵핑되어 관리하려는 외래키(TEAM_ID)가 Member 테이블에 있음.

2. 추가로 날라가는 Update sql. 

고로 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자!

 

일대일[1:1]

아래의 경우(Member 가 주 테이블일 때 Locker는 대상 테이블, 대상 테이블에 외래키가 있을 때) 에는

Member 엔티티에 locker 값이 존재하는 지 알려면 Member 테이블만을 조회해서는 알 수 가 없다.(null을 넣어줘야하는지 Proxy를 넣어도 되는지 모름)

locker 를 확인을 해야되서 프록시기능의 한계로 지연로딩으로 설정해도 항상 즉시 로딩된다. 

일대다의 경우에는 FK가 Collection이기에 , 컬렉션 자체가 데이터가 없는 Empty를 표현할 수 있다.

 

내 엔티티에 있는 외래키는 내가 직접관리해야한다. 

다대일 양방향 매핑처럼 외래키가 있는곳이 연관관계의 주인 반대편은 mapppedby 적용

위에서 member 쪽에 외래키 가있는 것이 성능상유리하다. member를 조회할경우가 많은데 이미 조회했으니 locker가 존재하면 어떤 로직이 실행되게 결정할 경우가 많다. 

 

다대다[N:M] 사용안하는게 좋음

관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없음

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

객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능

연결테이블은 단순히 연결만 하고 끝나지 않음 , 주문시간, 수량같은 데이터가 들어올수있음 하지만 넣어줄수가 없다.

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;
    
    private int count;
    private int price;
    
    private LocalDateTime orderDateTime;

}

이렇게 중간에 엔티티 하나를 넣어주먄 원하는 수량이나 주문날짜 같은것을 여기에다 넣어주면된다.

왠만하며 PK는 아무 의미 없는 값을 써야한다~

 

실전예제 3 - 다양한 연관관계 매핑

Entity Relationship Diagram(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<>();

    public void addOrder(Order order)
    {
        orders.add(order);
        order.setMember(this);
    }

    public Long getId()
    {
        return id;
    }

    public void setId(Long id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getCity()
    {
        return city;
    }

    public void setCity(String city)
    {
        this.city = city;
    }

    public String getStreet()
    {
        return street;
    }

    public void setStreet(String street)
    {
        this.street = street;
    }

    public String getZipcode()
    {
        return zipcode;
    }

    public void setZipcode(String zipcode)
    {
        this.zipcode = zipcode;
    }
}
package jpabook.jpashop.domain;

import javax.annotation.processing.Generated;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "ORDERS")
public class Order
{
    @Id
    @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id;

//    @Column(name="MEMBER_ID")
//    private Long memberId;

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

    public void addOrderItem(OrderItem orderItem)
    {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;

    public Long getId()
    {
        return id;
    }

    public void setId(Long id)
    {
        this.id = id;
    }

    public Member getMember()
    {
        return member;
    }

    public void setMember(Member member)
    {
        this.member = member;
    }

    public LocalDateTime getOrderDate()
    {
        return orderDate;
    }

    public void setOrderDate(LocalDateTime orderDate)
    {
        this.orderDate = orderDate;
    }

    public OrderStatus getStatus()
    {
        return status;
    }

    public void setStatus(OrderStatus status)
    {
        this.status = status;
    }

}

FK가 둘다 이 테이블에 있기 때문에 매핑관계의 주인이 된다. 

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;

}

양방향 연결을 위한 mappedBy.

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 = "CATEGORY_ITEM",
    joinColumns = @JoinColumn(name = "CATEGORY_ID"),
    inverseJoinColumns = @JoinColumn(name = "ITEM_ID")
    )
    private List<Item> items = new ArrayList<>();

}

절대 쓰지말라는 다대다 매핑의 예시이다. JoinTable은 중간에 릴레이션 테이블이 있다고 가정하고 @JoinColumn을 해주는 것이다.

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<>();

    public Long getId()
    {
        return id;
    }

    public void setId(Long id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public int getPrice()
    {
        return price;
    }

    public void setPrice(int price)
    {
        this.price = price;
    }

    public int getStockQuantity()
    {
        return stockQuantity;
    }

    public void setStockQuantity(int stockQuantity)
    {
        this.stockQuantity = stockQuantity;
    }
}

객체의 경우에는 2개의 엔티티로 다대다 매핑이 가능함으로 그냥 Category 컬렉션으로 참조했다.

728x90