[자바 ORM 표준 JPA 프로그래밍] 6.4 다대다
개발서적/자바 ORM 표준 JPA

[자바 ORM 표준 JPA 프로그래밍] 6.4 다대다

Reference. 자바 ORM 표준 JPA 프로그래밍

책 목차 및 이전 글

더보기

6.4 다대다[N:N]

  • 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 없음
  • 다대다 관계 → 일대다, 다대일 관계로 풀어내는 연결 테이블을 사용

  • 객체는 테이블과 다르게 객체 2개로 다대다 관계를 만드는 것이 가능 (컬렉션 <-> 컬렉션)

6.4.1 다대다: 단방향

다대다 단방향 Member
@Entity
public class Member {
		@Id @Column(name = "MEMBER_ID");
		private String id;
		
		private String username;

		@ManyToMany
		@JoinTable(name = "MEMBER_PRODUCT",
				joinColumns = @JoinColumn(name = "MEMBER_ID"),
				inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))
		private List<Product> products = new ArrayList<Product>();
		...
}		
다대다 단방향 Product
@Entity
public class Product {
		@Id @Column(name = "PRODUCT_ID")
		private String id;

		private String name;
		...
}
  • 회원엔티티 - 상품 엔티티를 @ManyToMany로 매핑
  • @ManyToMany와 @JoinTable을 사용해서 연결 테이블을 바로 매핑 회원과 상품을 연결하는 회원_상품(Member_Product) 없이 매핑 완료

@JoinTable의 속성

  • @JoinTable.name: 연결 테이블을 지정, 위에 예제에선 MEMBER_PRODUCT 테이블을 선택
  • @JoinTable.joinColumns: 현재 방향인 회원에서 매핑할 조인 컬럼을 지정 (MEMBER_ID)
  • @JoinTable.inverseJoinColumns: 반대 방향인 상품과 매핑할 조인 컬럼을 지정 (PRODUCT_ID)

다대다 단방향 저장
public void save() {
		Product productA = new Product();
		productA.setId("productA");
		productA.setName("상품A");
		em.persist(productA);
		
		Member member1 = new Member();
		member1.setId("member1");
		member1.setUsername("회원1");
		member1.getProducts().add(productA); //연관관계 설정
		em.persist(member1);
}

//실행 SQL
INSERT INTO PRODUCT ...
INSERT INTO MEMBER ...
INSERT INTO MEMBER_PRODUCT ...
다대다 단방향 탐색
public void find() {
		Member member = em.find(Member.class, "member1");
		List<Product> products = member.getProducts(); //객체그래프 탐색
		for (Product product : products){
				System.out.println("product.name = " + product.getName());
		}
}

//실행 SQL
SELECT * FROM MEMBER_PRODUCT MP
INNER JOIN PRODUCT P ON MP.PRODUCT_ID=P.PRODUCT_ID
WHERE MP.MEMBER_ID = ?

6.4.2 다대다:양방향

  • 역방향도 @ManyToMany를 사용, 양쪽 중 원하는 곳에 mappedBy로 연관관계의 주인을 지정
  • 양방향 연관관계를 만들었으므로 역방향으로 객체 그래프 탐색이 가능
//다대다 양방향 연관관계 설정
member.getProducts().add(product);
product.getMembers().add(member);

//양방향 연관관계는 편의 메소드를 추가하는 것이 편리
public void addProduct(Product product) {
		...
		products.add(product);
		product.getMembers().add(this);
}

//역방향 탐색
public void findInverse() {
		Product product = em.find(Product.class, "productA");
		List<Member> members = product.getMembers();
		...
}

6.4.3 다대다: 매핑의 한계와 극복, 연결 엔티티 사용

  • @ManyToMany로 자동으로 연결테이블을 처리하면 편리하지만 실무에서 사용하기 어려움
    • 연결 테이블에 단순히 아이디만 담는 것이 아닌 주문수량, 날짜 등 추가로 컬럼을 필요로 함

  • 추가 컬럼 문제를 해결하기 위해 다대다 → 일대다, 다대일 관계로 풀어야 해결 가능

    Member
    @Entity
    public class Member {
    		@Id @Column(name = "MEMBER_ID")
    		private String id;		
    
    		//역방향
    		@OneTomany(mappedBy = "member")
    		private List<MemberProduct> memberProducts;
    		...
    }
    Product
    @Entity
    public class Product {
    		@Id @Column(name = "PRODUCT_ID")
    		private String id;
    		
    		...
    }
    MemberProduct
    @Entity
    @IdClass (MemberProductId.class)
    public class MemberProduct {
    		@Id
    		@ManyToOne
    		@JoinColumn(name = "MEMBER_ID")
    		private Member member; //MemberProductID.member와 연결
    
    		@Id
    		@ManyToOne
    		@JoinColumn(name = "PRODUCT_ID")
    		private Product product; //MemberProductId.product와 연결
    		
    		private int orderAmount;
    		...
    }
    MemberProductId
    public class MemberProductId implements Serializable {
    		private String member;  //MemberProduct.member와 연결
    		private String product; //MemberProduct.product와 연결
    
    		//hashCode and equals Override
    }

복합 기본 키

  • 복합 기본 키(복합키) : 기본키가 MEMBER_ID와 PRODUCT_ID같이 여러개로 이뤄진 것을 의미
  • JPA에서 복합 키를 사용하면 별도 식별자 클래스 생성이 필요
    • @IdClass를 사용해서 식별자 클래스를 지정

<복합 키를 위한 식별자 클래스의 특징>

  • 복합 키는 별도의 식별자 클래스를 생성
  • Serializable 구현 필요
  • equals와 hashCode 메소드를 구현
  • 기본 생성자가 필요
  • 식별자 클래스는 public이어야 함
  • @IdClass를 사용하는 방법 외에 @EmbeddedId를 사용하는 방법도 존재

식별 관계

부모 테이블의 기본 키를 받아 자신의 기본 키 + 외래 키로 사용하는 것을 DB 용어로 식별 관계

회원상품은 회원의 기본 키 + 상품의 기본 키 = 복합 기본 키로 사용 (동시에 외래키로 사용)

저장하는 코드
public void save() {
		//회원 저장
		Member member1 = new Member();
		member1.setId("member1");
		member1.setUsername("회원1");
		em.persist(member1);

		//상품 저장
		Product productA = new Product();
		productA.setId("productA");
		productA.setName("상품1");
		em.persist(productA);

		//회원상품 저장
		MemberProduct memberProduct = new MemberProduct();
		memberProduct.setMember(member1); //주문 회원 - 연관관계 설정
		memberProduct.setProduct(productA); //주문 상품 - 연관관계 설정
		memberProduct.setOrderAmount(2); //주문 수량
		em.persist(memberProduct);
}
조회 코드
  • 복합 키항상 식별자 클래스를 생성하고 em.find()식별자 클래스로 엔티티를 조회
  • 복합 키의 사용은 복잡, @IdClass 또는 @EmbeddedId가 필요하고 식별자 클래스도 구현 필요
public void find() {
		//기본 키 값 생성
		MemberProductId  memberProductId = new MemberProductId();
		memberProductId.setMember("member1");
		memberProductId.setProduct("productA");

		MemberProduct memberProduct = em.find(MemberProduct.class, memberProductId);

		Member member = memberProduct.getMember();
		Product product = memberProduct.getProduct();

		//결과 출력 코드 
		...
}

6.4.4 다대다:새로운 기본키 사용

  • 추천하는 기본키 생성 전략은 DB에서 자동으로 생성해주는 대리 키를 Long으로 사용하는 방법
  • 이것을 사용하면 간편하고 영구히 사용가능, 비즈니스에 의존적이지 않는 장점
  • 복합키(MEMBER_ID, PRODUCT_ID) 대신 ORDER_ID인 대리키를 사용

    @Entity
    public class Order {
    		@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 orderAmount;
    		...
    }
    저장 코드
    • 복합키를 사용했을 때와 차이는 없음
    public void save() {
    		//회원 저장
    		Member member1 = new Member();
    		member1.setId("member1");
    		member1.setUsername("회원1");
    		em.persist(member1);
    
    		//상품 저장
    		Product productA = new Product();
    		productA.setId("productA");
    		productA.setName("상품1");
    		em.persist(productA);
    
    		//주문 저장
    		Order order = new Order();
    		order.setMember(member1); //주문 회원 - 연관관계 설정
    		order.setProduct(productA); //주문 상품- 연관관계 설정
    		order.setOrderAmount(2); //주문 수량
    		em.persist(order);
    }
    조회 코드
    • 식별자 클래스 없이 고유 아이디로 조회 가능
    public void find() {
    		Long orderId = 1L;
    		Order order = em.find(Order.class, orderId);
    
    		Member member = order.getMember();
    		Product product = order.getProduct();
    
    		//결과 출력 코드 
    		...
    }

6.4.5 다대다 연관관계 정리

  • 식별 관계 : 받아온 식별자를 기본 키 + 외래 키로 사용
  • 비식별 관계 : 받아온 식별자는 외래 키로만 사용, 새로운 식별자를 추가

비식별 관계를 사용하는 것이 복합 키 없이 단순하고 편리한 ORM 매핑이 가능 이러한 이유로 추천