본문 바로가기
Spring Framework/JPA

영속성 관리

by 도쿠니 2022. 6. 10.

JPA가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계 부분매핑한 엔티티를 실제 사용하는 부분으로 나눌 수 있습니다.

여기서는 매핑한 엔티티를 EntityManager를 통해 사용하는 법을 정리하려합니다.

여기서는 Hibernater를 기준으로 정리합니다.

구조

  • 요청이 올 때마다 EntityManagerFactory를 통해 EntityManager를 생성합니다.
  • EntityManager는 내부적으로 DB 커넥션을 통해 DB를 사용합니다.

Entity Manager Factory

  • EntityManager를 생성합니다.
  • 생성 비용이 크기 때문에 하나만 생성해 애플리케이션 전체에서 공유하여 사용합니다.
    • 설정 정보를 읽어 JPA를 동작시키기 위한 기반 객체 생성 및 커넥션 풀 생성 등으로 생성 비용이 큽니다.
      • J2SE 환경에서는 커넥션 풀을 생성하고, J2EE 환경에서는 스프링 컨테이너가 제공하는 데이터소스를 사용합니다.
    • 데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 하나만 생성합니다.
    • 여러 스레드가 동시에 접근해도 안전합니다.

Entity Manager

  • 엔티티를 저장, 수정, 삭제, 조회하는 등 엔티티와 관련된 모든 일을 처리합니다.
  • EntityManagerFactory를 통해서 생성합니다.
  • 생성 비용이 거의 들지 않습니다.
  • 여러 스레드가 동시에 접근하면 동시성 문제가 발생하기 때문에 스레드 간에 절대 공유하면 안됩니다.
  • 데이터베이스 연결이 꼭 필요한 시점에 데이터베이스 커넥션을 얻습니다.
    • 보통 트랜잭션을 시작할 때 커넥션을 획득합니다.
// EntityManagerFactory 생성
// 파라미터로 persistence.xml 파일의 persistence-unit name 을 넣어줍니다.
EntityManagerFactory emf = Persistence.createEntityMangerFactory("persistence-unit name");

// EntityManager 생성
EntityManager em = emf.createEntityManager();

Persistence Context

  • 영속성 컨텍스트라는 의미로 엔티티를 영구 저장하는 논리적인 환경을 뜻합니다.
  • EntityManager를 통해 Persistence Context에 접근하고 관리합니다.
  • EntityManager로 Entity를 저장하거나 조회하면 EntityManager는 Persistence Context에 Entity를 보관하고 관리합니다.
  • EntityManager를 생성할 때마다 하나씩 만들어집니다.
    • 엔티티 매니저 안에 논리적인 저장소가 하나 있다고 생각하면 됩니다. 
    • 여러 EntityManager가 같은 Persistence Context에 접근할 수도 있습니다.

 

Entity LifeCycle

Entity는 4가지 상태를 갖습니다.

  • 비영속 (new / transient)
    • Persistence Context와 전혀 관계가 없는 상태
    // 객체를 막 생성한 상태
    Member member = new Member();
    
  • 영속 (managed)
    • Persistence Context에 저장된 상태
    • persist()를 통해서 엔티티를 Persistence Context가 관리하도록 합니다.
    // Persistence Context가 객체를 관리
    em.persist(member);
    
  • 준영속 (detached)
    • Persistence Context에 저장되었다가 분리된 상태
    • Persistence Context가 엔티티를 관리하지 않습니다.
      • 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않습니다.
      • 이미 한번 영속상태였기 때문에 식별자 값을 가지고 있습니다.
      • 영속성 컨텍스트가 관리하지 않으므로 지연로딩도 할 수 없습니다.
    • detach() , clear() , close() 로 엔티티를 준영속 상태로 만들 수 있습니다.
    • merge() 를 통해 준영속 상태의 엔티티를 다시 영속 상태로 변경할 수 있습니다.
    // 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않는 상태
    em.detach(member);
    
    // 준영속 상태 -> 영속 상태
    em.merge(member);
    
    // 영속성 컨텍스트를 초기화하거나
    em.clear();
    
    // 영속성 컨텍스트를 닫으면 영속성 컨텍스트가 관리하던 엔티티는 준영속 상태가 됩니다.
    em.close();
  • 삭제 (removed)
    • 삭제된 상태
    // 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
    em.remove(member);

 

Persistence Context 특징

  • 엔티티를 식별자 값으로 구분합니다.
    • 영속 상태는 반드시 식별자 값이 있어야 합니다.
  • 트랜잭션을 커밋할 때 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영합니다.

 

Persistence Context 이점

  • 1차 캐시
    • 영속성 컨텍스트는 내부에 캐시를 가지고 있습니다.
    • 영속 상태의 엔티티는 모두 이곳에 저장됩니다.
    • 1차 캐시의 키는 식별자 값이고 식별자 값은 데이터베이스 기본 키와 매핑되어있습니다.
      • 엔티티 조회 시, 먼저 1차 캐시에서 찾아 반환합니다.
      • 없으면 데이터베이스에서 조회하여 엔티티를 생성하고 1차 캐시에 저장한 후 영속 상태의 엔티티를 반환합니다.
    • 성능상 이점은 크게 없다고 합니다.
      • EntityManager를 트랜잭션 단위로 만들고 트랜잭션이 종료되면 삭제되는데, 1차 캐시도 EntityManager와 생명주기를 같이 합니다.
      • 많은 요청이 들어오더라도 요청들은 각자의 트랜잭션을 가지면서 처리되기 때문에 1차 캐시로 데이터를 재사용하는 것은 트랜잭션 별로 찰나의 순간이라 크게 성능상 이점은 없다고 볼 수 있습니다.
// 먼저 1차 캐시에서 엔티티를 찾습니다.
// 없으면 데이터베이스에서 조회하여 엔티티를 생성하고 1차캐시에 저장한 후 영속 상태의 엔티티를 반환합니다.
Member member = em.find(Member.class,"member1");

 

  • 동일성 보장
    • 동일한 식별자로 조회하면 1차 캐시에 있는 같은 엔티티 인스턴스를 반환합니다.
      • 동일성 : 참조 값 비교, 실제 인스턴스가 동일
      • 동등성 : 값 비교, 실제 인스턴스는 다를 수도
Member a = em.find(Member.class,"member1");
Member b = em.find(Memeber.class,"member1");

System.out.println(a == b) // true

 

  • 트랜잭션을 지원하는 쓰기 지연
    • 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 SQL을 모아두었다가 커밋할 때 모아둔 쿼리를 데이터베이스로 보냅니다.
      • 트랜잭션을 커밋하면 엔티티 매니저는 우선 영속성 컨텍스트를 flush 합니다.
        • flush 란 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업을 뜻합니다.
      • 그 후, 실제 데이터베이스 트랜잭션을 커밋하며 데이터베이스에 반영됩니다.
// 엔티티 매니저로 트랜잭션을 생성합니다.
EntityTransaction transaction = em.getTransaction();

// 데이터 변경을 트랜잭션 내에서 처리되어야하기 때문에 트랜잭션을 시작합니다.
transaction.begin();

// 1차 캐시에 저장만 해두고 데이터베이스로 보낼 SQL은 따로 보관합니다.
em.persist(memberA);
em.persist(memberB);


// 트랜잭션을 커밋합니다. 데이터베이스로 SQL이 전송되며 반영됩니다.
transaction.commint();

Tip. 후에 다루겠지만 hibernate 설정에  insert/update 쿼리를 원하는 개수만큼 모아서 DB에 날릴 수 있는 옵션이 있습니다.

스프링 부트 application.yml 
spring: jpa: properties: hibernate: jdbc: batch_size: 100

 

  • 변경 감지(Dirty Checking)
    • 엔티티 수정 시,자바 컬렉션 다루듯 단순히 엔티티를 조회해서 데이터만 변경하면 자동으로 데이터베이스에 반영됩니다.
      • JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는데 이를 스냅샷이라 합니다.
      • flush 시점에 스냅샷과 엔티티를 비교하여 변경된 엔티티를 찾고 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보냅니다.
    • 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용됩니다.
    • 수정 시, 엔티티의 모든 필드를 업데이트 합니다.
      • 하나의 필드를 변경하더라도 모두 업데이트합니다.
        • 모든 필드를 대상으로한 쿼리문이 나갑니다.
      • 모든 필드를 업데이트하면 수정 쿼리가 항상 같기때문에 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있으며 데이터베이스는 동일한 쿼리를 받으면 이전에 한번 파싱된 쿼리를 재사용할 수 있습니다.
      • 필드가 많거나 저장되는 내용이 너무 크면 수정된 데이터만 사용해서 동적으로 UPDATE SQL을 생성하는 전략을 선택하면 됩니다.
        • @DynamicUpdate : 데이터 저장 시, 수정된 데이터만으로 sql을 동적 생성
          • 컬럼이 30개 이상일 때 사용하면 정적 수정 쿼리보다 더 빠름
        • @DynamicInsert : 데이터 저장 시, null이 아닌 필드만으로 sql을 동적 생성
Member member = em.find(Member.class,"member1");

// 변경 감지로 엔티티 수정
member.setName("dokuny");

@Entity
@org.hibernate.annotation.**DynamicUpdate** // 수정된 데이터만 사용해서 동적으로 SQL을 생성하는 어노테이션
public class Member{}

더티 체킹 과정

  • 지연 로딩
    • 자바 객체 내의 다른 객체를 실제로 사용할 때 불러오는 것을 지연 로딩이라 합니다.
      • 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법입니다. 
      • 쿼리가 많이 발생합니다.
      • 즉시로딩은 join을 통해서 연관된 객체를 다같이 가져오는 방식입니다.
      • 실무에서는 일단 지연로딩으로 설정해둔뒤 최적화가 필요한 부분에 즉시로딩을 사용합니다.

 

Flush

  • 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것을 의미합니다.
  • flush()를 실행하면 변경 감지가 동작한 후, 모아둔 쿼리를 데이터베이스에 전송합니다.(등록, 수정 , 삭제 쿼리)
  • flush하는 방법은 3가지 입니다.
    • 직접 호출
    • 트랜잭션 커밋시 자동 호출
    • JPQL 쿼리 실행 시 자동 호출
  • FlushModeType으로 모드를 설정할 수 있습니다.
// FlushModeType.AUTO : 커밋,쿼리 실행 시 플러시 (기본값)
//FlushModeType.COMMIT : 커밋 시에만 플러시

// 모드 설정 방법
em.setFlushMode(FlushModeType.COMMIT);
  • flush를 하더라도 1차 캐시는 유지됩니다.

'Spring Framework > JPA' 카테고리의 다른 글

Transaction (트랜잭션)  (0) 2022.06.10
엔티티 매핑  (0) 2022.06.10
데이터베이스 방언 (Dialect)  (0) 2022.06.10
JPA 소개  (0) 2022.06.10
Test용 H2 DB application.yml 설정  (0) 2022.06.09

댓글