ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] 영속성 컨텍스트(Persistence Context)와 주요 기능
    JPA 2023. 5. 13. 00:02

    [JPA] 영속성 컨텍스트(Persistence Context)와 주요 기능


    안녕하세요? 장장스입니다.

    오늘은 JPA 영속성 컨텍스트와 주요 기능에 대해 정리해보겠습니다.

     

    영속성 컨텍스트(Persistence Context)란?


    JPA(Java Persistence API)에서 영속성 컨텍스트(Persistence Context)란 엔티티(Entity)를 영구 저장하는 환경을 말한다. 

     

    여기서 엔티티(Entity)는 데이터베이스의 테이블에 대응하는 클래스로. JPA에서는 @Entity 어노테이션이 붙은 클래스를 엔티티라 한다.

     

    @Entity
    public abstract class Item{
        @Id @GeneratedValue
        @Column(name = "ITEM_ID")
        private Long id;
        private String name;
        private int price;
        private int stockQuantity;
    }

     

     

    엔티티(Entity) 매니저


    엔티티 매니저(Entity Manager)를 통해 영속성 컨텍스트에 접근할 수 있으며, 엔티티 매니저는 영속성 컨텍스트(Persistence Context)내에서 엔티티(Entity)를 관리한다.

     

    EntityManagerFactory emf = Persistence.createEntityManagerFactory("emf");
    EntityManager em = emf.createEntityManager();

     

    데이터베이스의 데이터를 변경하는 모든 명령은 트랜잭션 안에서 이루어져야 한다. 엔티티 매니저로부터 트랜잭션을 얻을 수 있다.

    EntityTransaction tx = em.getTransaction();
    
    tx.begin(); // 트랜잭션 시작
    tx.commit(); // 트랜잭션 수행
    tx.rollback(); // 작업에 문제 발생 시

     

    엔티티 매니저는 데이터베이스 연결이 필요한 시점에 커넥션 풀에서 커넥션(connection)을 얻어서 사용한다.

    참고로, JPA의 구현체(hibernate) 는 EntityManagerFactory를 생성할 때 커넥션 풀을 생성한다.

     

     

    엔티티의 생명주기


    비영속(new/transient)

    영속성 컨텍스트와 상관 없이 단순히 객체를 생성한 상태를 말한다.

    Member member = new Member();

     

    영속(managed)

     

    영속성 컨텍스트에 영속된 상태를 말한다.

    em.persist(member);

     

     

    준영속(detached)

    영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 상태를 말한다.

    em.detach(member); // 특정 엔티티를 준영속 상태로 전환
    em.clear(); //영속성 컨텍스트 초기화
    em.close(); //영속성 컨텍스트 종료

    영속성 컨텍스트가 제공하는 기능을 사용하지 못하기 때문에 비영속 상태와 유사하다. 다만, 준영속 상태는  영속 상태였을 때의 식별자 값을 가지고 있다. 준영속 상태를 다시 영속 시키려면 merge 메서드를 사용한다.

    em.merge(member);

     

     

    삭제(removed)

    영속성 컨텍스트를 삭제한 상태를 말한다.

    em.remove(member);

     

     

     

    1차 캐시(First Level Cache)


    영속성 컨텍스트의 가장 주요 기능은 캐시 기능을 제공하는 것이다. 1차 캐시는  영속성 컨텍스트 내부에 존재하는 캐시로, 데이터베이스에서 데이터를 가져오는 것이 아닌 영속성 컨텍스트 내부에서 데이터를 가져오는 기능입니다.

     

    1차 캐시를 사용함으로서 성능상의 이점이 발생 하지만, 1차 캐시는 동일 트랜잭션 내에서만 유지되기 때문에 보통 성능상 큰 차이가 발생하지 않는다.

     

    동일성 보장

    1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다. 따라서 자바의 컬렉션과 같이 동일 트랜잭션에서 엔티티의 동일성을 보장해준다.

    Member a = em.find(Member.class, "member1"); 
    Member b = em.find(Member.class, "member1");
    System.out.println(a == b); //동일성 비교 true

     

     

    쓰기 지연(Write Behind)


    쓰기 지연(Write Behind)은 트랜잭션을 커밋할 때까지 SQL 쿼리를 모아서 한번에 DB에 전달하는 방식을 말한다.

     

    // 1.트랜잭션 시작
    transaction.begin(); 
    
    // 2. 엔티티 영속성 컨텍스트 등록
    em.persist(memberA);
    em.persist(memberB);
    //---여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.---
    
    // 3. 트랜잭션 커밋 - 데이터베이스에 INSERT SQL을 보낸다.
    transaction.commit();

     

     

    em.persist() 메서드가 수행되면 다음과 같은 동작이 일어난다. 

        1. 영속성 컨텍스트 1차 캐시에 저장
        2. 쓰기 지연 SQL 저장소에 INSERT SQL 을 생성하여 보관

     

    마지막으로 transaction.commit() or em.flush()이 실행되면 쓰기 지연 SQL 저장소의 SQL들을 한꺼번에 데이터베이스로 날린다. 쓰기 지연은 일종의 버퍼링 기능으로 I/O 부하를 줄일 수 있다는 것이 장점이다.

    다만, 너무 많은 I/O가 동시에 발생하면 DB에 부하를 줄 수도 있다. 하이버네이트에서는 최대 한번에 보낼 insert/update 사이즈를 설정하는 옵션을 제공한다.

    <property name="hibernate.jdbc.batch_size" value="10"/>

     

    변경 감지(Dirty Checking)


    JPA는 엔티티가 변경되면 수정 메서드를 사용하지 않아도 자동으로 UPDATE 쿼리를 날리게 된다.

     

    // 1.트랜잭션 시작
    transaction.begin();
    
    // 2.영속 엔티티 조회
    Member memberA = em.find(Member.class, "memberA");
    // 3. 영속 엔티티 데이터 수정
    memberA.setUsername("hi");
    memberA.setAge(10);
    
    // 3-1. em.update(member) 이런 코드가 있어야 하지 않을까?
    
    // 4. 트랜잭션 커밋
    transaction.commit();

     

    마치 자바 컬렉션에 있는 값을 꺼내서 변경하면 컬렉션에 다시 저장하지 않아도 컬렉션의 값이 수정되어 있는 것과 동일하게 동작한다. 이러한 JPA의 변경 감지(Dirty Checking)는 영속성 컨텍스트와 밀접한 관련이 있다.

    transaction.commit() 명령이 호출되기 전에  em.flush() 메서드가 호출된다. 

     

    플러시(flush)

    플러시가 발생하면 영속성 컨텍스트에서는 최초 데이터 1차 캐시에 저장된 상태의 스냅샷과 현재 엔티티의 값을 비교하는 동작이 수행된다. 엔티티의 값이 변경이 된 경우라면 UPDATE 쿼리를 생성하여 SQL 쓰기 지연 저장소에 저장하고,  데이터베이스에 쿼리를 보내게 된다.

     

    영속성 컨텍스트를 플러시 하는 방법은 총 3가지 이다.

      1. 직접 flush() 호출

      2. transaction.commit()시 flush() 자동 호출

      3. JPQL 쿼리를 실행하게 되면 flush() 자동 호출

     

    JPQL 쿼리 실행시 flush()가 자동으로 호출되는 이유는 무엇일까?

    JPQL은 영속성 컨텍스트에 적용되는 것이 아니라 데이터베이스에 직접 쿼리를 날리게 된다. 영속성 컨텍스트에 있지만 flush() 되지 않은 객체를 찾는 쿼리를 날릴 경우 객체가 DB에 없어 조회되지 않기 때문에 JPQL 쿼리를 실행하게 되면 자동으로 flush()를 자동으로 호출하게 된다.

     

     

     

    Post


    •  

    References


     

     


    잘못된 코드나 내용이 있다면 댓글을 남겨주세요. 즉시 수정하도록 하겠습니다! :)

     

    댓글