JPA

JPA 에서 준영속 상태란? 실무에서 왜 중요할까?

경딩 2025. 5. 8. 23:36

JPA를 사용하다 보면 "준영속 상태"라는 개념을 접하게된다.

처음엔 어렵게 느껴지지만, 실무에서 엔티티를 수정할 때 꼭 이해하고 넘어가야 할 개념이다.

이번 글에서는 준영속 상태가 무엇인지, 왜 중요한지, 그리고 실무에서는 어떻게 다뤄야 하는지 알아보자.


엔티티란?

  • JPA 에서 Entity 는 데이터베이스 테이블에 매핑되는 자바 클래스를 의미한다.
  • @Entity, @Id 애노테이션을 통해 정의하며, 하나의 객체 인스턴스는 테이블의 한 row를 의미한다.
@Entity
public class Book {
    @Id
    private Long id;
    private String name;
    private int price;
    // 기타 필드 및 getter/setter 생략
}

 

특징

엔티티는 EnitityManager 에 의해 관리되어야 DB 에 저장, 수정, 삭제가 가능하다.

 

 

JPA 의 엔티티 생명주기

JPA의 엔티티는 다음과 같은 생명주기를 가진다

  1. 비영속 (new): 아직 영속성 컨텍스트에 저장되지 않은 상태
  2. 영속: EntityManager를 통해 관리되고 있는 상태
  3. 준영속 (detached): 한 번 저장된 적이 있지만, 현재는 EntityManager가 관리하지 않는 상태
  4. 삭제: 삭제 명령이 수행된 상태

 

 

준영속 상태란?

이미 DB 에 저장된 적이 있는 객체인데, 현재 영속성 컨텍스트가 관리하고 있지 않은 상태

 

아래 코드와 같은 Book 객체도 준영속이라고 정의할까?

@PostMapping("items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form, @PathVariable String itemId) {

    Book book = new Book();

    book.setIsbn(form.getIsbn());
    book.setAuthor(form.getAuthor());
    book.setStockQuantity(form.getStockQuantity());
    book.setPrice(form.getPrice());
    book.setName(form.getName());
    book.setId(form.getId()); // DB에 있던 ID

    itemService.saveItem(book);

    return "redirect:/items";
}

정답은 준영속 상태이다.

준영속 상태 구별의 핵심은 식별자를 기준으로 영속상태가 되어서 DB에 저장된 적이 있는가이다.

준영속이라는 단어는 객체를 new 했거나, 안했거나를 기준으로 나누는 것은 아니다.

 

겉보기에넌 new Book() 으로 새 객체처럼 보이지만, ID 가 있는 객체이므로 DB 에 이미 저장된 적 있는 엔티티이다. 그러나 EntityManager 가 이를 관리하고 있지 않으므로 준영속 상태이다.

 

 

준영속 vs new 상태

구분 new 상태 준영속 상태
ID 존재 없음 있음
영속성 컨텍스트 관리 여부
merge() 동작 새로운 row 생성 기존 row를 찾아 병합
변경 감지 가능

준영속 상태 동작 원리

new 상태인 객체와 준영속 상태의 객체는 merge() 라는 명령에서 동작하는 방식이 다르다.

new 상태인 객체는 merge() 를 호출할 때 완전히 새로운 엔티티를 만든다.

반면 준영속 상태의 엔티티는 DB 에서 기존 엔티티를 찾고 그 값을 준영속 상태의 객체로 변경한 후에 반환하다.

마치 준영속 상태의 객체가 영속상태가 되는 것과 같다.

변경 감지와 병합(merge)

병합은 모든 필드를 변경해버리고, 데이터가 없으면 null 로 업데이트 해버린다. (주의)

병합을 사용하면서 이 문제를 해결하려면, 변경 폼화면에서 모든 데이터를 항상 유지해야한다.

실무에서는 보통 변경가능한 데이터만 노출하기 때문에, 병합을 사용하는 것이 오히려 번거롭다.

 

따라서 엔티티를 변경할 때는 항상 변경감지를 사용해야한다.

  • 컨트롤러에서 어설프게 엔티티를 생성하지 말자.
  • 트랜잭션이 있는 서비스 계층에 식별자와 변경할 데이터를 전달하자.(파라미터 or dto)
  • 트랜잭션이 있는 서비스 계층에서 영속 상태 엔티티를 조회하고, 엔티티의 데이터를 직접 변경하자.
  • 트랜잭션 커밋 시점에 변경감지가 실행된다.
@Transactional
public void updateItem(Long id, String name, int price) {
    Book book = entityManager.find(Book.class, id); // 영속 상태
    book.setName(name); // 변경 감지
    book.setPrice(price);
}

 

 

The save method anti-pattern

@Transactional
public void saveAntiPattern(Long postId, String postTitle) {
⠀
    Post post = postRepository.findById(postId).orElseThrow();
⠀
    post.setTitle(postTitle);
⠀
    postRepository.save(post);
}

 

jpa 에서는 엔티티 변경에 대해선 dirty checking 을 통해 update 하는 것이 권장되는 패턴이다.

이미 영속 상태에 있는 엔티티를 update 하려면 가장 효율적인 방식은 더킹 체킹이다.

영속 상태 엔티티는 필드값만 수성해도 자동으로 update 쿼리가 발생하니, 이 방식이 가장 효율적이다.

 

만약 save 를 호출한다면 pk 값이 이미 있기 때문에 내부적으로 merge 가 수행되고 그로 인해 불필요한 

select 쿼리가 발생할 수 있으며 cascade 병합 오버헤드도 발생할 수 있다.

(https://vladmihalcea.com/best-spring-data-jparepository/)

 

마무리 요약

  • new로 객체를 만들었더라도, 식별자(ID)가 존재하면 준영속 상태일 수 있다.
  • 준영속 상태는 EntityManager가 관리하지 않는 과거 영속 객체
  • 병합(merge)은 신중하게 사용할 것 → 모든 필드를 유지해야 함
  • 실무에서는 변경 감지(dirty checking) 방식이 더 안전하고 권장된다.
 

 

 

참고자료: https://www.inflearn.com/community/questions/70393/book-%EA%B0%9D%EC%B2%B4%EA%B0%80-%EC%99%9C-%EC%A4%80%EC%98%81%EC%86%8D%EC%9D%B8%EA%B2%83%EC%9D%B8%EA%B0%80

'JPA' 카테고리의 다른 글

공통 인터페이스 기능  (1) 2024.12.07
양방향 연관관계와 연관관계 주인  (0) 2024.12.05
[JPA] 영속성 컨텍스트  (0) 2024.11.16