프로젝트를 진행하면서 기본키는 인조키로 따로 가져가는 방법을 택했는데 그래서 복합키의 사용을 하지 못했기 때문에 이것에 대해 배우고자 직접 실습해보았다.
보통 다대다 관계에서는 직접 @ManyToMany로 지정하는 경우는 거의 사용하지 않는다고 해서 중간에 추가적인 엔티티를 만들어서 사용하였다. 이번 테스트에 사용한 엔티티는 다음과 같다.
엔티티 (Entity)
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "categories")
public class Category {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Builder.Default
@OneToMany(mappedBy = "category")
private Set<CategoryItem> categoryItemSet = new HashSet<>();
}
Category 엔티티
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "items")
public class Item {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Builder.Default
@OneToMany(mappedBy = "item")
private Set<CategoryItem> categoryItemSet = new HashSet<>();
}
Item 엔티티
@NoArgsConstructor
@IdClass(CategoryItemId.class)
@Entity
@Table(name = "category_item")
public class CategoryItem {
@Id
@ManyToOne
@JoinColumn(name = "category_id")
private Category category;
@Id
@ManyToOne
@JoinColumn(name = "item_id")
private Item item;
public CategoryItem(Category category, Item item) {
this.category = category;
this.item = item;
category.getCategoryItemSet().add(this);
item.getCategoryItemSet().add(this);
}
}
CategoryItem 엔티티
양방향 매핑을 위해서 생성자에 category와 item의 categoryItemSet에 인스턴스를 추가하는 기능을 만들었다.
복합키의 경우 엔티티에서는 @Id만 지정하고 @IdClass나 @EmbeddedId로 복합키로 지정할 필드를 따로 클래스로 만들어 지정해주어야 한다. 전자는 DB, 후자는 객체지향적인 성향이 강하다.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CategoryItemId implements Serializable {
private Long category;
private Long item;
}
CategoryItemId 식별자 클래스
Serializable 인터페이스를 구현하고, equals와 hashCode를 구현해야 한다. 나는 lombok의 @Data를 이용하여 만들었다. 추가적으로 기본 생성자가 있어야 하고, 접근 제한자는 public이어야 한다.
테스트
@SpringBootTest
public class CategoryItemTest {
@Autowired
private EntityManager entityManager;
@Autowired
private PlatformTransactionManager txManager;
@Test
@Transactional
public void save() {
//TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
// 카테고리
Category category = Category.builder().name("가전").build();
entityManager.persist(category);
// 아이템
Item item = Item.builder().name("tv").build();
entityManager.persist(item);
// 카테고리_아이템
CategoryItem categoryItem = new CategoryItem(category, item);
entityManager.persist(categoryItem);
//txManager.commit(status);
CategoryItemId categoryItemId = new CategoryItemId(category.getId(), item.getId());
Assertions.assertThat(entityManager.find(CategoryItem.class, categoryItemId)).isNotNull();
}
}


테스트에서 @Transactional을 붙이게 되면 모든 코드가 실행된 후 최종적으로 롤백이 되므로 DB에 저장이 안 된다.
EntityManager.persist()는 영속성 컨텍스트에 저장하는 메서드로 DB에 바로 저장이 되는 것이 아니다. DB에 해당 내용이 반영되려면 다음 세가지 조건 중 하나여야 한다.
- EntityManager의 flush() 직접 호출
- commit으로 인한 flush() 자동 호출
- JPQL과 같은 객체 지향 쿼리 실행
위의 테스트에서는 카테고리와 아이템만 insert 쿼리가 실행되었는데 그 이유는 영속성 컨텍스트에서 관리되고 사용되는 엔티티의 경우 기본키가 무조건 있어야 하는데 DB에서 기본키의 자동 증가를 맡기는 경우인 GenerationType.IDENTITY는 먼저 DB에 데이터를 저장하고 식별자를 받아와야 한다. 그렇기 때문에 카테고리와 아이템은 insert 쿼리가 나간거고 categoryItem은 최종적으로 롤백되었기 때문에 insert 되지 않았다.
'공부 > JPA' 카테고리의 다른 글
| [JPA] OneToOne에서의 N + 1 문제 (0) | 2024.02.27 |
|---|---|
| [JPA] 프록시 (Proxy) (0) | 2024.02.24 |
| [JPA] 연관 관계 중 인조키 vs 복합키 에 대한 고민 (0) | 2024.02.20 |
| [JPA] 연관 관계 매핑 확인하기 (0) | 2024.02.16 |
| [JPA] 영속성 컨텍스트 (Persistence Context) 테스트하기 (1) | 2024.02.14 |