[JPA] @ManyToMany (다대다, M:N) 관계, 셀프 레퍼런스

dooboocookie·2022년 10월 28일
0
post-thumbnail

목표

  • 이 프로젝트에서 병원시,구,동엔티티를 참조한다.
    • 병원 엔티티는 시도 참조하고, 구도 참조한다.
    • M:N의 관계이다.
  • 특정 구는 특정 시의 자식과 같다.
    • ex. 서울시 - 강남구, 경기도 - 용인시 수지구

M:N 관계

테이블에서 M:N관계

  • 보통 M:N 관계라 하면 커머스에서 주문상품테이블로 생각해볼수 있겠다.
  • 한 상품은 여러 주문에 있을 수 있고,
  • 한 주문은 여러 상품을 주문할 수 있다.
  • 여기서 참조키를 어느 쪽에 줄지가 애매하다.

  • RDBMS 테이블 입장에서는 하나의 레코드에서는 하나의 FK만 가질 수 있으므로 무조건 중간 테이블이 필요하다.

객체에서 @ManyToMany

  • 객체 입장에서는 M:N 관계가 자연스럽게 된다.
  • 연과관계의 주인은 Order쪽으로 하겠다.
  • Order.java
@Entity
public class Order {

	@Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
     
 	//...
    
    @ManyToMany
    @JoinTable(name = "order_item")
    private List<Item> items = new ArrayList<>();
    
}
  • Item.java
@Entity
public class Item {

	@Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
     
 	//...
    
    @ManyToMany(mappedBy= "items")
    private List<Order> orders = new ArrayList<>();
    
}
  • 이런식으로 M:N 관계를 설정해 줄 수 있다.
  • 이렇게되면 DB상으로는 중간 테이블을 JPA가 CREATE문을 통해 만들게 된다.

@ManyToMany의 한계

  • 조인 테이블은 일단 확장이 불가능하다.
  • 비즈니스적으로 필요한 컬럼을 추가할 수 없다.
  • 심지어 생성일, 수정일 같은 컬럼도 추가할 수 없다.
  • 객체 입장에서는 너무 편리하지만 실무에서는 사용이 어렵다.

해결 1

  • 위와 같은 주문 - 상품의 경우 조인 테이블을 엔티티로 만들어, N:1 관계를 2개 만들면 된다.
  • OrderItem.java
@Entity
public class OrderItem {

	@Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
     
 	//...
    
    @ManyToOne
    @JoinColumn(name = "order_id")
    private Order order;
    
    @ManyToOne
    @JoinColumn(name = "item_id")
    private Item item;
    
}
  • 각각에 @ManyToMany 는 제거하고, 조인테이블을 엔티티로 만들어서 @ManyToOne같은 관계를 지어준다.

해결 2

  • 현재 우리 프로젝트의 경우 M:N 관계이긴 하지만 중간테이블을 만들지 않고 더 괜찮은 방법을로 해결할 수 있다.
  • 병원이 시, 구 중에 하나만 참조하면 되는 것이다.
  • 그렇다면, 보다는 더 상세한 를 참조하는 것이 나을 것이다.
  • 하지만 그렇다고해도 병원도 연관관계가 있다.
  • 이때는 셀프 레퍼런스로 해결해보자.

셀프 레퍼런스

  • 자기 참조
  • 부모, 즉 상위레벨의 자기 자신 엔티티를 참조해서 계층적인 테이블을 만든다.

  • Sigudong.java
public class Sigudong {

    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String sigudongName;

    @ManyToOne(fetch = LAZY) 
    @JoinColumn(name = "parent_id")
    private Sigudong parent;
    
    @OneToMany(mappedBy = "parent")
    private List<Sigudong> child = new ArrayList<>();
}

@Entity
public class Hospital {
	
    @Id 
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long hosId;

    private String hosName;

	//...
    
    @ManyToOne
    @JoinColumn(name = "sigudong_id")
    private Sigudong sigudong;
    
}
  • 이렇게 계층적으로 셀프 참조를 하면, 병원쪽에서는 제일 하위 레벨의 시, 구, 동을 참조하면 해결이 된다.
profile
1일 1산책 1커밋

0개의 댓글