이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.
JPA(Java Persistence API)에서 변경 감지(Dirty Checking)와 병합(Merge)은 엔티티 상태 관리를 위한 중요한 개념이다. 이 두 가지는 데이터베이스와 애플리케이션 간의 동기화를 효율적으로 관리하는 데 사용된다.
결론부터 말하면 두 방법중에 변경 감지를 사용하는 것이 더 좋다.
병합
병합(Merge)은 JPA에서 준영속 상태의 엔티티를 영속성 컨텍스트에 포함시키고, 해당 엔티티의 상태를 데이터베이스에 반영하는 작업이다.
이 과정은 데이터베이스에 이미 저장된 데이터를 수정할 때 사용된다.
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form) {
Book book = new Book();
book.setId(form.getId());
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());
itemService.saveItem(book);
return "redirect:/items";
}
Book 객체는 Item의 자식 객체임
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
private final EntityManager em;
@Transactional
public void saveItem(Item item) {
itemRepository.save(item);
}
}
@Repository
@RequiredArgsConstructor
public class ItemRepository {
private final EntityManager em;
public void save(Item item) {
if (item.getId() == null) {
em.persist(item);
} else {
//파리미터로 넘어온 객체는 준영속 상태의 엔티티
em.merge(item); //병합
}
}
마지막 코드의 save 메서드는 엔티티의 id 값을 기준으로 새로운 엔티티인지 기존 엔티티인지를 판단하여 각각 persist 또는 merge를 사용한다.
->item.getId() == null일 경우 (persist)
id가 null인 경우, 이는 새로 생성된 엔티티라는 의미이므로, JPA의 persist 메서드를 사용하여 새로운 엔티티를 영속성 컨텍스트에 추가하고 데이터베이스에 삽입한다.
이 때, persist는 영속성 컨텍스트에서 관리되지 않는 새로운 엔티티를 영속 상태로 만든다.
->item.getId() != null일 경우 (merge)
id가 null이 아닌 경우, 이는 이미 데이터베이스에 저장된 엔티티가 수정되었음을 의미한다.
즉, 기존에 id를 저장하고 있고 영속성 컨텍스트에서 관리되는 객체(A)가 존재했는데, 다른 새로운 객체가 동일한 id를 갖고 있는 객체(B)가 있는 것이다.
이때 B 객체는 수정된 엔티티이고, 영속성 컨텍스트에서 관리되지 않는 준영속 상태의 객체이다.
->기존에 A상태였는데 B 상태로 변경하고 싶은 상황인 것이다.
그러한 준영속 상태의 객체 item을 merge() 메서드를 이용하여 병합하면 기존에 영속 상태이던 객체(A)를 찾고, A에 저장하고 있던 값을 모두 NULL로 만든다. 이후 준영속 상태였던 객체(B)의 값을 A에 병합(값 채우기) 한다.
Merge 개념 중간 정리
-영속성 컨텍스트에서 관리되는 기존 객체(A)가 존재하는 상황에서, 동일한 ID를 가진 수정된 엔티티인 새로운 객체(B)가 있을 수 있다.
-이때 B 객체는 준영속 상태의 객체로, 영속성 컨텍스트가 관리하지 않는다.
-병합을 통해 B 객체의 상태를 영속성 컨텍스트에 복사하여, 기존의 영속 상태 객체(A)에 반영하고, 이를 통해 데이터베이스와 동기화한다. 병합된 후의 객체는 영속성 컨텍스트에서 관리되며, 이후 변경 사항이 자동으로 데이터베이스에 반영된다.
마지막으로 merge() 메서드로 반환된 객체는 영속상태이다. 왜냐하면 기존 영속 상태의 객체를 수정한 것이니까.(B는 여전히 준영속 상태)
이때 주의해야할 점이있다.
병합은 변경된 부분 외에 다른 부분도 데이터베이스에 반영한다.
기존에 영속 상태이던 객체(A)를 찾고, A에 저장하고 있던 값을 모두 NULL로 만들고 준영속상태였던 객체(B)의 값을 A에 병합(값 채우기)하기 때문에, 예를 들어 A에는 5개의 값이 있고, B에는 3개의 값이 있다면 2개의 값은 NULL로 변경된다.
자세한 예시를 살펴보자
아래의 코드는 위에 있던 코드이다.
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form) {
Book book = new Book();
book.setId(form.getId());
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());
itemService.saveItem(book);
return "redirect:/items";
}
여기서 book의 set 메서드를 하나라도 누락한다면 그 필드의 값은 A에 병합되지 않는다.
즉, 병합은 변경된 부분 외에 다른 부분도 데이터베이스에 반영한다.
변경 감지
변경 감지는 JPA의 EntityManager가 관리하는 영속성 컨텍스트 내의 엔티티가 변경되었는지 자동으로 감지하는 기능이다.
영속성 컨텍스트에 속한 엔티티는 영속 상태에 있으며, 이 상태의 엔티티는 자동으로 변경 감지 대상이 된다.
@PostMapping(value = "/items/{itemId}/edit")
public String updateItem(@PathVariable Long itemId, @ModelAttribute("form") BookForm form) {
itemService.updateItem(itemId, form.getName(), form.getPrice(), form.getStockQuantity());
return "redirect:/items";
}
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ItemService {
private final ItemRepository itemRepository;
private final EntityManager em;
//...
@Transactional
public void updateItem(Long id, String name, int price, int stockQuantity) {
Item item = itemRepository.findOne(id);
item.setName(name);
item.setPrice(price);
item.setStockQuantity(stockQuantity);
}
}
@Repository
@RequiredArgsConstructor
public class ItemRepository {
private final EntityManager em;
//...
public Item findOne(Long id) {
return em.find(Item.class, id);
}
}
ItemService의 updateItem 메서드
@Transactional로 지정되어 있어 메서드 내에서 트랜잭션이 관리된다.
- itemRepository.findOne(id)
주어진 id로 Item 엔티티를 데이터베이스에서 조회한다. 반환된 item 객체는 영속 상태에 있으며, 영속성 컨텍스트에서 관리된다. - 엔티티 필드 변경
item.setName(name), item.setPrice(price), item.setStockQuantity(stockQuantity)를 호출하여 엔티티의 필드를 변경한다.
이 변경은 영속성 컨텍스트에 의해 추적되며, 트랜잭션이 커밋될 때 변경 감지 메커니즘에 의해 자동으로 데이터베이스에 반영된다.
JPA의 변경 감지 메커니즘
JPA의 변경 감지 메커니즘은 영속 상태의 엔티티가 트랜잭션 범위 내에서 변경될 때, 변경된 부분을 자동으로 감지하고 데이터베이스에 반영하는 기능이다. 이는 주로 다음과 같은 단계를 통해 이루어진다.
1. 영속 상태의 엔티티가 조회될 때, 스냅샷이 생성되어 초기 상태가 저장된다.
2. 엔티티의 필드가 변경되면, 트랜잭션 커밋 시점에 JPA가 스냅샷과 현재 상태를 비교하여 변경된 부분을 감지한다.
3. 변경된 부분이 있다면, 해당 필드에 대해 SQL UPDATE 쿼리를 생성하고 실행하여 데이터베이스에 반영한다.
즉, 파라미터로 넘어온 객체의 id를 조회해서 영속 상태인 객체를 찾아내고, 영속 상태인 객체의 값을 변경해서 DB의 내용을 수정하는 방법이다.
병합과 달리 변경된 부분만 데이터베이스에 반영되므로 성능이 좋다.
반대로 말하면 병합은 변경된 부분 외에 다른 부분도 데이터베이스에 반영된다.
'Java Category > JPA' 카테고리의 다른 글
[JPA] 컬렉션 조회 최적화(OneToMany) (0) | 2024.08.11 |
---|---|
[JPA] 지연 로딩과 조회 성능 최적화(ManyToOne, OneToOne) (0) | 2024.08.10 |
[JPA] JPQL 고급 (0) | 2024.08.03 |
[JPA] JPQL 기본 문법 (0) | 2024.07.29 |
[JPA] 값 타입(Value Type) (0) | 2024.07.28 |