twocowsong

다 대 다 : 단방향 본문

IT/JPA

다 대 다 : 단방향

WsCode 2022. 6. 21. 21:49

다 대 다 단방향 관계인 회원 엔티티를 보겠습니다.

@Entity
public class Product {
   @Id
   @Column(name = "PRODUCT_ID")
   private String id;

   private String name;
}
@Entity
public class Member {

   @Id
   @GeneratedValue
   @Column(name="MEMBER_ID")
   private Long 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>();

   // Getter, Setter...
}

회원 엔티티와 상품 엔티티를 @ManyToMany로 매핑하였습니다.

여기서 중요한 점은 @ManyToMany와 @JoinTable을 사용해서 연결 테이블을 바로 매핑한 것입니다.

 

따라서 회원과 상품을 연결하는 회원_상품(MEMBER_PRODUCT)엔티티 없이 매핑을 완료할 수 있습니다.

연결 테이블을 매핑하는 @JoinTable 속성을 정리해보겠습니다.

 

@JoinTable.name : 연결 테이블을 지정합니다. 여기서는 회원_상품 테이블을 선택하였습니다.

@JoinTable.joinColumns : 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정합니다.

@JoinTable.inverseJoinColumns : 반대 방향인 상품과 매핑할 조인 컬럼정보를 지정하였습니다.

 

MEMBER_PRODUCT 테이블은 다대다 관계를 일대다, 다대일 관계로 풀어내기 위해 필요한 연결 테이블일 뿐입니다.

@ManyToMany로 매핑한 덕분에 다대다 관계를 사용할 때는 이연결 테이블을 신경 쓰지 않아도 됩니다.

 

이제 다대다 관계를 저장하는 예제입니다.

 

public static void save(EntityManager em) {
   Product productA = new Product();
   productA.setId("productA");
   productA.setName("상품 A");
   em.persist(productA);

   Member member1 = new Member();
   member1.setId(1L);
   member1.setUsername("회원1");
   member1.getProducts().add(productA); // 연관관계 설정
   em.persist(member1);
}

회원 1과 상품 A의 연관관계를 설정했으므로 회원1을 저장할 때 연결 테이블에도 값이 저장됩니다.

따라서 이 코드를 실행하면 다음과 같은 SQL이 실행됩니다.

MEMBER_PRODUCT도 INSERT되는것을 확일할수 있습니다.

INSERT INTO PRODUCT ..
INSERT INTO MEMBER ..
INSERT INTO MEMBER_PRODUCT ..

public static void find(EntityManager em) {
   Member member = em.find(Member.class, "member1");
   List<Product> products = member.getProducts(); // 객체 그래프 탐색

   for (Product product : products) {
      System.out.println("TestSave.find : " + product.getName());
   }
}

저장한 후에 탐색해두었던 상품1이 조회됩니다.

member.getProducts()를 호출해서 상품이름을 출력하면 다음 SQL이 실행됩니다.

 

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

실행된 SQL을 보면 연결 테이블인 MEMBER_PRODUCT와  상품 테이블을 조인해서 연관된 상품을 조회합니다.

@ManyToMany 덕분에 복잡한 다대다 관계를 애플리케이션에서는 아주 단순하게 사용할 수 있습니다.

 



위 예제를 저는 똑같이 따라해보았습니다.

4개의 테이블이 생성되었으며 현재 정상적으로 INSERT되지 않고있습니다. 에러는 아래와같습니다.

...

22:02:16.068 [main] DEBUG org.hibernate.service.internal.SessionFactoryServiceRegistryImpl - EventListenerRegistry access via ServiceRegistry is deprecated.  Use `sessionFactory.getEventEngine().getListenerRegistry()` instead
22:02:16.076 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Initializing SessionFactoryRegistry : org.hibernate.internal.SessionFactoryRegistry@78fe204a
22:02:16.078 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Registering SessionFactory: b5ccdee9-d8ec-435c-aecb-17df6602dd78 (<unnamed>)
22:02:16.079 [main] DEBUG org.hibernate.internal.SessionFactoryRegistry - Not binding SessionFactory to JNDI, no JNDI name configured
22:02:16.142 [main] DEBUG org.hibernate.stat.internal.StatisticsInitiator - Statistics initialized [enabled=false]
22:02:16.147 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - On TransactionImpl creation, JpaCompliance#isJpaTransactionComplianceEnabled == false
22:02:16.147 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - begin
22:02:16.153 [main] DEBUG org.hibernate.event.internal.AbstractSaveEventListener - Generated identifier: productA, using strategy: org.hibernate.id.Assigned
22:02:16.168 [main] DEBUG org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl - JDBC transaction marked for rollback-only (exception provided for stack trace)
java.lang.Exception: exception just for purpose of providing stack trace
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.markRollbackOnly(JdbcResourceLocalTransactionCoordinatorImpl.java:324)
	at org.hibernate.engine.transaction.internal.TransactionImpl.markRollbackOnly(TransactionImpl.java:203)
	at org.hibernate.internal.AbstractSharedSessionContract.markForRollbackOnly(AbstractSharedSessionContract.java:428)
	at org.hibernate.internal.ExceptionConverterImpl.handlePersistenceException(ExceptionConverterImpl.java:297)
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:155)
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
	at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
	at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:762)
	at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:742)
	at com.jpa.study.entity.TestSave.save(TestSave.java:46)
	at com.jpa.study.entity.TestSave.main(TestSave.java:23)
22:02:16.168 [main] DEBUG org.hibernate.engine.transaction.internal.TransactionImpl - rolling back
22:02:16.169 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction
22:02:16.170 [main] DEBUG org.hibernate.resource.jdbc.internal.LogicalConnectionManagedImpl - Initiating JDBC connection release from afterTransaction

아직 해결하지못한상황이며 추후 해결시에 본글을 수정해놓겠습니다.

'IT > JPA' 카테고리의 다른 글

다 대 다 : 매핑의 한계와 극복, 연결 엔티티 사용  (0) 2022.06.26
다 대 다 : 양방향  (0) 2022.06.26
다 대 다 [N : N]  (0) 2022.06.20
대상 테이블에 외래 키  (0) 2022.06.19
주 테이블 외래 키  (0) 2022.06.19