JPA 연관관계 매핑 - 복합키 매핑


복합키 매핑

데이터베이스의 테이블을 설계 할때에는 식별관계와 비식별관계 두가지 방법으로 설계를 할 수 있습니다. 식별 관계와 비식별 관계에 대해서 정리 해보도록 하겠습니다.

식별관계와 비식별관계

  • 식별관계

    식별관계 구조

    식별관계 매핑은 부모테이블의 키를 자식테이블이 내려 받아서 기본키 + 외래키 형태로 사용 하는 매핑 방식 입니다.
    부모의 키가 자식 테이블로 상속 되면서 테이블의 키가 많아지는 단점이 있고 유연하지 못하기 때문에 꼭 필요 한 곳에서만 사용 되고 있습니다.

  • 비식별관계

    비식별관계 구조

    비식별관계 매핑은 부모 테이블의 기본키를 자식 테이블의 외래키로만 사용 하는 매핑 방식 입니다.
    비식별관계는 NULL을 허용하는 선택적 비식별관계 과 NULL을 허용 하지 않는 필수적 비식별관계로 나누어 집니다.

복합키 매핑 하기

  • JPA 는 복합키를 매핑 하기위해서 아래 두가지 어노테이션을 제공 합니다.
    어떤 방식을 사용 하던 상관은 없으나 각각 장단점이 있기 때문에 예제를 보고 확인 해보도록 하겠습니다.

    어노테이션 설명
    @IdClass 관계형 데이터 베이스에 가까운 방법
    @EmbeddedId 객체 지향에 가까운 방법
  • 복합키 매핑을 사용 하기 위한 조건

    • @IdClass, @EmbeddedId 어노테이션 추가
    • Serializable 인터페이스 구현(equals(), hashCode() 구현)
    • 식별자 클래스는 public 이여야함.

식별 관계 매핑 하기

  • @IdClass 사용

    • @IdClass 를 사용 하는 경우에는 식별자 클래스를 지정 해야 합니다.
      //부모
      @Entity
      public class Shop {
         @Id
         @Column(name = "SHOP_SEQ")
         private int seq;
         private String name;
          //...getter, setter
      } 
       
      //자식1
      @Entity
      @IdClass(PaymentID.class)
      public class Shop_Payments {
          @Id
          @Column(name="PAY_SEQ")
          private int paySeq;
          private String name;
            
          @Id @ManyToOne
          @JoinColumn(name = "SHOP_SEQ")
          private Shop shop;
          //...getter, setter
      }
      
      //식별자 클래스
      public class PaymentID implements Serializable{
         private Shop shop;    //Shop_Payments.shop 매핑
         private int paySeq;   //Shop_Payments.paySeq 매핑
         //...hashCode(), equals
      }
       
      //자식2
      @Entity
      @IdClass(OrderID.class)
      public class Shop_Order {
          @Id
          private int orderSeq;
          private String name;
            
          @Id
          @ManyToOne
          @JoinColumns({
              @JoinColumn(name="SHOP_SEQ"),
              @JoinColumn(name="PAY_SEQ")
          })
          private Shop_Payments payments;
          //...getter, setter
      }
        
      //식별자 클래스
      public class OrderID implements Serializable{
         private Shop_Payments payments;  //Shop_Order.payments 매핑
         private int orderSeq;            //Shop_Order.orderSeq 매핑
         //...hashCode(), equals
      }
    
    • 테스트 코드
      Shop shop = new Shop();
      shop.setSeq(1);
      shop.setName("JPA마켓");
      em.persist(shop);
        
      Shop_Payments payment = new Shop_Payments();
      payment.setPaySeq(1);
      payment.setName("JPA간편페이");
      payment.setShop(shop);
      em.persist(payment);
        
      Shop_Order order = new Shop_Order();
      order.setOrderSeq(1);
      order.setName("봉골레");
      order.setPayments(payment);
      em.persist(order);
    
  • @EmbeddedId 사용

    • @EmbeddedId 를 적용한 식별자 클래스를 사용하는 경우에는 식별자 클래스에 기본 키를 직접 매핑 해야 합니다.
      @Entity
      public class Shop {
          @Id @GeneratedValue
          @Column(name = "SHOP_SEQ")
          private int seq;
          private String name;
          //...getter, setter
      }
        
      //자식1
      @Entity
      public class Shop_Payments {
      	@EmbeddedId
      	private PaymentID paySeq;
      	private String name;
        	
      	@MapsId("shopSeq")      //PaymentID.shopSeq 매핑
      	@ManyToOne
      	@JoinColumn(name = "SHOP_SEQ")
          private Shop shop;
          //...getter, setter
      }
        
      //Serializable 인터페이스 구현
      @Embeddable
      public class PaymentID implements Serializable{
      	@Column(name="PAY_SEQ")
      	private int paySeq;    	
      	private int shopSeq;	//@MapsId("shopSeq") 매핑
        
      	public PaymentID() {}
      	public PaymentID(int shopSeq, int paySeq) {
      		this.shopSeq = shopSeq;
      		this.paySeq = paySeq;
      	}
          //...hashCode(), equals
      }
       
      //자식1
      @Entity
      public class Shop_Order {
      	@EmbeddedId
      	private OrderID orderSeq;
      	private String name;
        	
      	@MapsId("paySeq")      //OrderID.paySeq 매핑
      	@ManyToOne
      	@JoinColumns({
              @JoinColumn(name="SHOP_SEQ"),
              @JoinColumn(name="PAY_SEQ")
          })
      	private Shop_Payments payment;
          //...getter, setter
      }
       
      @Embeddable
      public class OrderID implements Serializable {
      	@Column(name = "ORDER_SEQ")
      	private int orderSeq;    
      	private PaymentID paySeq; // @MapsId("paySeq") 매핑
    
      	public OrderID() {}
      	public OrderID(int orderSeq, PaymentID paySeq) {
      		super();
      		this.orderSeq = orderSeq;
      		this.paySeq = paySeq;
      	}
          //...hashCode(), equals
      }
    
    • 테스트 코드
          Shop shop = new Shop();
          shop.setName("JPA마켓");
          em.persist(shop);
            
          PaymentID id = new PaymentID(1,1);
          Shop_Payments payment = new Shop_Payments();
          payment.setPaySeq(id);
          payment.setName("JPA간편페이");
          payment.setShop(shop);
          em.persist(payment);
            
          OrderID orderId = new OrderID(1, id);
          Shop_Order order = new Shop_Order();
          order.setOrderSeq(orderId);
          order.setName("봉골레");
          order.setPayment(payment);
          em.persist(order);
    
    • 테스트코드를 수행한 결과는 (@IdClass, @EmbeddedId) 동일합니다.

    테스트결과

비식별 관계 매핑 하기

  • 위 예제를 바탕으로 비식별 관계로 변경 해보겠습니다.

      @Entity
      public class Shop {
          @Id @GeneratedValue
          @Column(name = "SHOP_SEQ")
          private int seq;
          private String name;
          //...hashCode(), equals
      }
        
      @Entity
      public class Shop_Payments {
          @Id @GeneratedValue
          @Column(name="PAY_SEQ")
          private int paySeq;
          private String name;
            
          @ManyToOne
          @JoinColumn(name = "SHOP_SEQ")
          private Shop shop;
          //...hashCode(), equals
      }
        
      //자식2
      @Entity
      public class Shop_Order {
          @Id @GeneratedValue
          @Column(name="ORDER_SEQ")
          private int orderSeq;
          private String name;
            
          @ManyToOne
          @JoinColumn(name="PAY_SEQ")
          private Shop_Payments payments;
          //...hashCode(), equals
      }
    
    • 테스트 코드
          Shop shop = new Shop();
          shop.setName("JPA마켓");
          em.persist(shop);
            
          Shop_Payments payment = new Shop_Payments();
          payment.setName("JPA간편페이");
          payment.setShop(shop);
          em.persist(payment);
            
          Shop_Order order = new Shop_Order();
          order.setName("봉골레");
          order.setPayments(payment);
          em.persist(order);
    
    • 테스트 결과

    테스트결과

    • 비식별 관계 매핑은 식별관계 매핑의 코드와 비교 하면 매우 쉽고 단순합니다. 식별자 클래스를 생성할 필요도 없고 복합 키 클래스를 만들 필요도 없습니다.

마무리

오늘은 식별관계 vs 비식별관계 매핑에 대해서 정리 해보았습니다.
식별관계 매핑은 자식이 늘어나면서 키 컬럼이 계속 늘어나고 변경이 쉽지 않으며 키의 자동생성 (@GeneratedValue)을 활용 하지 못하는 단점이 있기 때문에
데이터베이스 설계 관점에서는 비식별관계 매핑을 더 선호합니다.

자바 ORM 표준 JPA프로그래밍(저자:김영한) 도서를 학습하면서 정리 한 내용 입니다.

Back to blog