JPA는 dirty check(변경 감지) 기능이 탑재되어 있다.
// NewsController
@PostMapping("/admin/news/modify/{newsId}")
public String modify(@PathVariable("newsId") Long id, NewsForm newsForm){
newsService.modifyNews(id, newsForm);
return "redirect:/news/view/"+id;
}
// NewsService
@Transactional(readOnly = false)
public void modifyNews(Long id, NewsForm newsForm){
News news = newsRepository.find(id);
news.setTitle(newsForm.getTitle());
news.setContent(newsForm.getContent());
}
DB에서 id를 통해 조회한 News 데이터를 setXXX로 수정한 뒤
commit을 하게되면
JPA의 영속성 컨텍스트의 dirty check 기능 때문에 자동으로 영속성 컨텍스트에서 변경된 사항을 체크하고
update 시킨 뒤에 DB에 em.flush() 해준다
영속성 컨텍스트에서 제외되어버리는 상태를 준영속 상태라고 말한다.
영속성 컨텍스트(entity manager)에 없는 객체를 find 하게되면,
DB에서 조회를 해서 영속성 컨텍스트에 저장을 하게 되는데,
준영속 상태(detached)가 되어버리면, 영속성 컨텍스트가 제공하는 여러 기능을 사용하지 못한다.
dirty checking, 1차 캐시, 동일성 보장, batch 와 같은 기능을 사용할 수 없게 된다.
em.clear()를 통해 강제로 준영속 상태로 만들 수 있다.
@PostMapping("/admin/news/modify/{newsId}")
public String modify(@PathVariable("newsId") Long id, NewsForm newsForm){
News news = newsService.find(id);
news.setTitle(newsForm.getTitle());
news.setContent(newsForm.getContent());
newsService.write(news);
return "redirect:/news/view/"+id;
}
위와 같은 코드가 준영속 상태이다.
왜냐하면 newsService.find(id)
한 시점에서 이미 해당 news 데이터는 DB에서 불려져 온 상태이다. 이때 식별자를 달고 불려진다.
식별자가 달려있는 상태에서 commit()이 되어버리면 해당 객체는 준영속상태(detahced)가 되어버린다.
이 상태에서는 dirty check 이외에 다른 영속성 컨텍스트 기능들을 사용할 수 없다.
JPA의 손에서 벗어났기 때문이다.
newsSerivce.find()를 사용한 순간 commit()이 된다.
왜냐하면 해당 메서드가 트랜잭션 단위이기 때문이다.
따라서 find 메서드를 요청하면 한번의 트랜잭션을 수행한 것이고
그말은 즉슨, commit() 되었다는 의미이다.
tip
: transaction은 모든 로직을 끝마치면 commit()한다.
JPA의 변경감지 기능을 이용하는 코드는 맨 위에서 이미 소개했다.
병합
: 준영속 상태의 엔티티를 영속 상태 엔티티로 변경하는 것을 의미한다.
merge의 동작과정은 아래와 같다.
파라미터로 전달받은 식별자 값(newsService.find(id)에서 id를 의미
)으로 1차 캐시를 조회하고,
없다면 DB에서 조회한 다음 해당 엔티티를 영속성 컨텍스트에 놓는다.
영속석 컨텍스트에 놓여진 엔티티는 사용자가 수정한 데이터 값으로 병합된채로 반환된다.
이것이 merge의 동작 방식이다.
또한, 반환되는 것에 집중해야 하는데 해당 엔티티 타입으로 병합(수정)된 값을 반환하기 때문에
사용자는 그 값을 사용해야 한다.
또한, merge는 자동으로 flush() or persist() 해주지 않기 떄문에 DB에 저장해야 한다면
따로 flush()나 persist()를 선언해줘야 한다.
병합의 특성상 엔티티의 모든 속성을 변경시키기 때문에 사용자가 어떤 속성을 값을 입력하지 않고 엔티티를 저장하게 되면
해당 속성 값이 null로 데이터베이스에 저장되게 된다.
이게 큰 문제가 되는 이유는
클라이언트 측에서 실수 또는 고의로 값을 입력하지 않게되면 이를 병합하는 과정에서 모든 속성이 변경될 것이고
변경되지 않은 속성 값이 null로 변경되어 예상치 못한 상황이 발생할 수 있다
가능하면 변경감지를 통해 데이터를 수정하자.