군만두의 IT 공부 일지

[스터디10] 03. 영속성 관리 본문

프로그래밍/Java

[스터디10] 03. 영속성 관리

mandus 2025. 7. 4. 07:24

목차

    3장. 영속성 관리

    3.1 엔티티 매니저 팩토리와 엔티티 매니저

    • 데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 EntityManagerFactory를 하나만 생성한다.
    • 엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간 공유해도 되지만, 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안 된다.
    // 엔티티 매니저 팩토리 생성, 비용이 아주 많이 든다.
    EntityManagerFactory emf =
    	Persistence.createEntityManagerFactory("jpabook");
        
    // 엔티티 매니저 생성, 비용이 거의 안 든다.
    EntityManager em = emf.createEntityManager();

    3.2 영속성 컨텍스트란?

    • 영속성 컨텍스트(persistence context): 엔티티를 영구 저장하는 환경
    • 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
    • persist() 메소드는 엔티티 매니저를 사용해서 엔티티를 영속성 컨텍스트에 저장한다.
    • 영속성 컨텍스트는 엔티티 매니저를 생성할 때 만들어지고, 엔티티 매니저를 통해 접근 및 관리할 수 있다.

    3.3 엔티티의 생명주기

    엔티티에는 4가지 상태가 있다.

    • 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
    • 영속(managed): 영속성 컨텍스트에 저장된 상태
    • 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
    • 삭제(removed): 삭제된 상태
    // 비영속 상태
    Member member = new Member();
    member.setId("member1");
    member.setUsername("회원1");
    
    // 영속 상태
    em.persist(member);
    
    // 준영속 상태
    em.detach(member);
    
    // 삭제 상태
    em.remove(member);

    ▲ 엔티티의 생명 주기
    ▲ 비영속 상태와 영속 상태

    3.4 영속성 컨텍스트의 특징

    • 영속성 컨텍스트와 식별자 값: 영속성 컨텍스트는 엔티티를 식별자 값으로 구분한다. 영속 상태는 식별자 값이 반드시 있어야 한다. (없으면 예외 발생)
    • 영속성 컨텍스트와 데이터베이스 저장: JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영한다. (플러시, flush)
    • 영속성 컨텍스트가 엔티티를 관리할 때 장점
      • 1차 캐시
      • 동일성 보장
      • 트랜잭션을 지원하는 쓰기 지연
      • 변경 감지
      • 지연 로딩

    3.4.1 엔티티 조회

    • 1차 캐시: 영속성 컨텍스트가 내부에 가지고 있는 캐시. 영속 상태의 엔티티는 모두 이곳에 저장된다.
    • 1차 캐시의 키식별자 값이고 식별자 값은 데이터베이스 기본키와 매핑되어 있으므로, 영속성 컨텍스트에 데이터를 저장/조회하는 모든 기준은 데이터베이스 기본키 값이다.
    • 영속성 컨텍스트는 성능상 이점과 엔티티의 동일성(identity, ==)을 보장한다.
    // 1차 캐시에서 조회
    Member a = em.find(Member.class, "member1");  // DB 조회 후 1차 캐시 저장
    Member b = em.find(Member.class, "member1");  // 1차 캐시에서 조회
    
    System.out.println(a == b);  // true (동일성 보장)

    3.4.2 엔티티 등록

    • 트랜잭션을 지원하는 쓰기 지연(transactional write-behind): 엔티티 매니저가 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 등록 쿼리를 모았다가, 트랜잭션을 커밋할 때 모아둔 등록 쿼리를 데이터베이스에 보내는 것
    • 트랜잭션을 지원하는 쓰기 지연이 가능한 이유: 어떻게든 커밋 직전에만 데이터베이스에 SQL을 전달하면 되기 때문이다.

    3.4.3 엔티티 수정

    • SQL 수정 쿼리의 문제점: SQL을 사용하면 수정 쿼리를 직접 작성해야 한다. 요구사항이 늘어나면서 수정 쿼리가 많아지는 것은 물론이고, 비즈니스 로직을 분석하기 위해 SQL을 계속 확인해야 한다. 직간접적으로 비즈니스 로직이 SQL에 의존하게 된다.
    • 변경 감지(dirty checking): 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능. 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다. (비영속, 준영속은 반영X)
    • 스냅샷: JPA가 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 저장해서 저장해두는 것
    • JPA의 기본 전략은 엔티티의 모든 필드를 업데이트한다.
      • 장점: 모든 필드를 사용하면 수정 쿼리가 항상 같다. 쿼리를 재사용할 수 있다.
      • 단점: 데이터베이스에 보내는 데이터 전송량이 증가한다.
    • 상황에 따라 컬럼이 많으면 기본 방법인 정적 수정 쿼리보다 @DynamicUpdate를 사용한 동적 수정 쿼리가 빠르다. 하지만 한 테이블에 컬럼이 많다는 것은 테이블 설계상 책임이 적절히 분리되지 않았을 가능성이 높다.

    3.4.4 엔티티 삭제

    • remove()에 삭제 대상 엔티티를 넘겨주면 엔티티 등록과 비슷하게 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록한다. 이후 트랜잭션을 커밋해서 플러시를 호출하면 실제 데이터베이스에 삭제 쿼리를 전달한다.
    • remove()를 호출하는 순간 영속성 컨텍스트에서 제거된다. 삭제된 엔티티는 재사용하지 말고 자연스럽게 가비지 컬렉션의 대상이 되도록 두는 것이 좋다.

    3.5 플러시

    • 플러시(flush()): 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
    • 영속성 컨텍스트를 플러시하는 방법
      1. em.flush()를 직접 호출한다. → 거의 사용하지 않는다.
      2. 트랜잭션 커밋 시 플러시가 자동 호출된다.
      3. JPQL 쿼리 실행 시 플러시가 자동 호출된다.
    • 식별자를 기준으로 조회하는 find() 메소드를 호출할 때는 플러시가 실행되지 않는다.

    3.5.1 플러시 모드 옵션

    엔티티 매니저에 플러시 모드를 직접 지정하려면 javax.persistence.FlushModeType(현재는 jakarta.persistence.FlushModeType)을 사용한다.

    • FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시(기본값)
    • FlushModeType.COMMIT: 커밋할 때만 플러시

    3.6 준영속

    • 준영속 상태: 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된(detached) 것
    • 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다.
    • 영속 상태의 엔티티를 준영속 상태로 만드는 방법
      1. em.detach(entity): 특정 엔티티만 준영속 상태로 전환한다.
      2. em.clear(): 영속성 컨텍스트를 완전히 초기화한다.
      3. em.close(): 영속성 컨텍스트를 종료한다.

    3.6.4 준영속 상태의 특징

    준영속 상태인 엔티티는 다음과 같다.

    • 거의 비영속 상태에 가깝다. (즉, 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다)
    • (영속 상태였으므로) 식별자 값을 가지고 있다.
    • 지연 로딩(실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법)을 할 수 없다.

    3.6.5 병합: merge()

    • 준영속 상태의 엔티티를 다시 영속 상태로 변경하려면 병합을 사용한다. 병합은 비영속 엔티티도 영속 상태로 만들 수 있다.
    • merge() 메소드는 준영속 상태의 엔티티를 받아서 새로운 영속 상태의 엔티티를 반환한다.
    • 식별자 값으로 엔티티를 조회할 수 있으면 불러서 병합하고 조회할 수 없으면 새로 생성해서 병합한다. (save or update)

     

    ✔️ 복습하기
    1. 영속성 컨텍스트란?
    2. 플러시란?
    3. 더티 체킹이란?

     

    이 글은 『 자바 ORM 표준 JPA 프로그래밍』 책을 학습한 내용을 정리한 것입니다.

     

    Comments