[뉴스 APP] JPA 변경 감지, 준영속 상태 detach, 병합(merge)

김성수·2023년 5월 11일
1

뉴스어플리케이션

목록 보기
3/5

JPA는 dirty check(변경 감지) 기능이 탑재되어 있다.

NewsController

// 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


// 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()를 통해 강제로 준영속 상태로 만들 수 있다.


Controller에서의 준영속 상태 예시

    @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의 손에서 벗어났기 때문이다.


언제 commit() 되었길래?

newsSerivce.find()를 사용한 순간 commit()이 된다.

왜냐하면 해당 메서드가 트랜잭션 단위이기 때문이다.

따라서 find 메서드를 요청하면 한번의 트랜잭션을 수행한 것이고

그말은 즉슨, commit() 되었다는 의미이다.

tip : transaction은 모든 로직을 끝마치면 commit()한다.


준영속 상태에서 데이터를 변경하는 것보다는 JPA의 변경감지 기능을 적극 이용하자.

JPA의 변경감지 기능을 이용하는 코드는 맨 위에서 이미 소개했다.


병합(merge) 사용

병합 : 준영속 상태의 엔티티를 영속 상태 엔티티로 변경하는 것을 의미한다.

merge의 동작과정은 아래와 같다.

파라미터로 전달받은 식별자 값(newsService.find(id)에서 id를 의미)으로 1차 캐시를 조회하고,

없다면 DB에서 조회한 다음 해당 엔티티를 영속성 컨텍스트에 놓는다.

영속석 컨텍스트에 놓여진 엔티티는 사용자가 수정한 데이터 값으로 병합된채로 반환된다.

이것이 merge의 동작 방식이다.


또한, 반환되는 것에 집중해야 하는데 해당 엔티티 타입으로 병합(수정)된 값을 반환하기 때문에

사용자는 그 값을 사용해야 한다.


또한, merge는 자동으로 flush() or persist() 해주지 않기 떄문에 DB에 저장해야 한다면

따로 flush()나 persist()를 선언해줘야 한다.



병합(merge)시 주의사항

병합의 특성상 엔티티의 모든 속성을 변경시키기 때문에 사용자가 어떤 속성을 값을 입력하지 않고 엔티티를 저장하게 되면

해당 속성 값이 null로 데이터베이스에 저장되게 된다.

이게 큰 문제가 되는 이유는

클라이언트 측에서 실수 또는 고의로 값을 입력하지 않게되면 이를 병합하는 과정에서 모든 속성이 변경될 것이고

변경되지 않은 속성 값이 null로 변경되어 예상치 못한 상황이 발생할 수 있다



결론

가능하면 변경감지를 통해 데이터를 수정하자.

profile
깊이 있는 소프트웨어 개발자가 되고 싶습니다.

0개의 댓글