Lee's Grow up

[JAVA/JPA] 영속성 컨텍스트 EntityManage JPA 동작 방식 본문

PROGRAMMING/JAVA

[JAVA/JPA] 영속성 컨텍스트 EntityManage JPA 동작 방식

효기로그 2019. 12. 7. 02:48
반응형

해당 내용은 인프런의 자바 ORM 표준 JPA 프로그래밍 - 기본편 , 김영한 의 내용을 기반으로 정리해서 작성한 글입니다.
자세한 내용은 해당 강의 또는 책을 구매하셔서 알아보시는걸 추천합니다.

해당 글을 읽기전 JPA 기본편 을 먼저 보시는걸 추천합니다.

 

[JAVA/JPA] JPA 와 ORM 기본 개념 Hibernate ORM

해당 내용은 인프런의 자바 ORM 표준 JPA 프로그래밍-기본편 - 김영한 의 내용을 기반으로 정리해서 작성한 글입니다. 자세한 내용은 해당 강의 또는 책을 구매하셔서 자세히 알아보시는걸 추천합니다. ORM 이란?..

lee1535.tistory.com

EntityManagerFactory와 EntityManager


  • 어플리케이션은 단 하나의 EntityManagerFactory를 가져야함. 즉 어플리케이션이 실행하면 생성하고 종료하면 소멸
  • EntityManager은 하나의 Thread(Transaction)가 생성될 때마다 EntityManagerFactory가 생성해준다.
  • EntityManager은 실제 DB 사용을 담당

영속성 컨텍스트 ?

  • 논리적인 개념, 눈에 보이지 않음
  • EntityManager.persist(Entity)를 사용하면 DB에 저장하는게 아닌 영속성 컨텍스트에 저장

엔티티의 생명주기

  • 비영속 : 객체가 영속성 컨텍스트와 관련이 없는 상태
    아래와 같이 EntityManger을 통해서 영속성 컨텍스트에 접근하지 않은 상태
    public void process(){
      Member mbmer = new Member();
    }
  • 영속 : 객체에 영속성 컨텍스트에 관리되고 있는 상태
    아래와 같이 EntityManger을 영속성 컨텍스트에 저장시킨 상태
    public void process(){
      Member mbmer = new Member();
      entityManager.persist(member);
    }
  • 준영속 : 영속 컨텍스트에 저장되어 있다가 분리된 상태
    아래와 같이 EntityManger을 영속성 컨텍스트에서 객체를 분리시킨 상태
    public void process(){
      Member mbmer = new Member();
      entityManager.detach(member);
    }
  • 삭제 : 삭제된 상태
    아래와 같이 EntityManger을 영속성 컨텍스트에서 객체를 완전히 삭제시킨 상태
    public void process(){
      Member mbmer = new Member();
      entityManager.remove(member);
    }

영속성 컨텍스트 사용 이유

왜 DB에 바로 저장하지 않고 중간 단계인 영속성 컨텍스트라를 통해서 객체를 저장할까?
아래와 같은 이점이 존재

이점
  1. 1차 캐시
    실제 EntityManager.persist(Entity)를 통해 객체를 저장하면 영속 컨텍스트에 저장만 시킨다
    그럼 언제 DB에 접근을 할까? 강조했던 commit시점에 영속 컨텍스트가 1차 캐쉬에 있는 내용을 한번에 DB에 저장시킨다.
    아래와 같은 상황에서 DB 접근이 몇번 생기는지 확인해보자

    public void process(){
       Member member = new Member();
     member.setId(100L);
     entityManager.persist(member);
    
     Member selectMember = entityManager.find(Member.class, 100L );
    }

    psersist()를 호출 했으니까 insert를 실행 할거 같고, 키 값이 100인 멤버를 find()를 통해 select 해올 것 같다. 하지만 영속 컨텍스트 동작 메커니즘에 의해 persist()시점에 객체는 영속성 컨텍스트에 저장이 된다.
    그 후 find()호출 시 DB에서 값을 가져오는게 아닌 영속성 컨텍스트를 참조해서 값을 가져온다. 그리고 마지막 commit 시점에 INSERT 쿼리가 1번만 실행되는 방식으로 동작, 그러나 단위가 트랜잭션 단위이기 때문에 엄청나게 성능 향상은 아님

  2. 객체의 동일성 보장

    public void process(){
       Member member = emtityManager.find(Member.class,100L);
     Member member = emtityManager.find(Member.class,100L);
    }

    1차 캐쉬를 통해 SELECT문도 1번만 발생, 1차 캐쉬를 통해 REPEATABLE READ 격리수준을 DB 차원이 아닌 어플리케이션 차원에서 제공해줌

  3. 트랜잭션을 지원하는 쓰기 지연
    1번 1차캐시에서 설명한 내용처럼 psersist()시점에 DB에 INSERT를 실행하는게 아닌 commit 시점에 추가/수정/삭제 쿼리를 발생 시킴, 이는 1차 캐쉬와 별도로 쓰기 지연 SQL 저장소라는 공간에서 SQL문을 관리하기 때문,
    이는 버퍼링의 기능을 사용함, DB에 여러번 접속이 아닌 JDBC.batch() 를 통해 한번에 쿼리를 동작시킴

  4. 변경 감지 ( Dirty Checking)
    아래와 같은 상황에서 언제 DB에 업데이트가 반영이 될까?

    public void process(){
     Member member = entityManager.find(Memeber.class,100L);
     member.setName("Lee's GrowUp");
     // entityManager.persist(member) ; 주석처리
    }

    사실 객체가 영속상태인 경우 entityManager.persist() 메소드 호출 없이 영속성 컨텍스트가 자동으로 UPDATE 쿼리를 발생 시킴, 이는 1차 캐쉬에 객체의스냅샷을 저장하는 영역이 추가로 있는데, commit 시점 또는 flush() 시점에 스냅샷과 현재 Entity를 비교해 다르면 UPDATE 쿼리를 발생시킴

  5. 지연 로딩( Lazy Loading )

    public void process(){
       Member member = entityManager.find(Member.class,100L);
     member.getDepartment(); // 필드로 Department 클래스를 가짐
    }

    위와 같이 Member 클래스가 필드로 Department를 가지는 경우, find(member) 시점에는 MEMBER SELECT만 발생하고, getDepartment()를 호출하는 순간 DEPARTMENT 를 SELECT한다.

플러시

그렇다면 EntityManager은 commit에서만 DB에 SQL을 동작시킬까? flush()를 사용하면 강제로 쓰기지연 SQL 저장소에 있는 SQL 문을 동작시킴

동작시점
  • entityManager.flush() 호출
  • 트랜잭션 커밋 : 자동호출
  • JPQL 쿼리 실행

위와 같은 경우에 flush()가 동작하며 영속성 컨텍스트에있는 값은 변경이 없고 쓰기지연 SQL 저장소의 값만 DB와 연동시키고 초기화 한다고 생각하면 됩니다.
보시면 JPQL이라고 JPA에서 지원하는 SQL 언어에서도 flush()가 동작한다고 했는데 이유는 무엇일까?
아래 코드를 보시면 문제점이 보일겁니다.

  public void process(){
      entityManager.persist(member1);
    entityManager.persist(member2);

    query = entityManger.createQuery("select m from member m",Member.class);
    List<Member> members = query.getResultList();
  }

위와 같은 코드에서 영속성 컨텍스트의 메커니즘을 이해하셨으면 문제가 보이실겁니다. 바로 해당 로직에서는 flush()가 동작하지 않았습니다. 즉, member1member2는 현재 DB에 저장되어 있지 않은 상태에서 쿼리를 실행해 DB에 값을 불러올 경우 데이터가 누락되는 현상이 발생하기 때문에 예외적으로 JPQL에서도 flush()가 동작합니다.

플러시 모드 설정

단 위와 같은 동작 방식이 불필요한경우, JPQL에서 flush()가 동작하지 않게 하는 방법도 존재합니다.

  • em.setFlushMode(FlushModeType.COMMIT) : 커밋시점에만 동작
  • em.setFlushMode(FlushModeType.AUTO) : 기본 동작

준영속 상태

영속성 컨텍스트에서 분리시킴으로써 필요한 로직에서 사용

public void process(){
    Member member = entityManager.find(Member.class,100L);
    member.setName("changeName");

    entityManager.detach(member);
}

원래라면 메커니즘에 의해 commit 시점에 UPDATE 쿼리가 발생해야 하지만, 해당 로직에서는 SELECT 문만 발생

비슷한 기능
  • entityManager.clear() : 영속성 컨텍스트를 전부 초기화
  • entityManager.close() : 영속성 컨텍스트를 종료
반응형
Comments