[자바 ORM 표준 JPA 프로그래밍 - 기본편] 03. 영속성 관리 - 내부 동작 방식

Turtle·2024년 6월 17일
0
post-thumbnail

🙄영속성 컨텍스트1

  • ✔️엔티티 매니저 팩토리와 엔티티 매니저
    • 엔티티 매니저 팩토리엔티티 매니저를 만드는 공간으로 한 개만 만들어서 애플리케이션 전체에서 공유하도록 설계되어 있다.
    • 이 엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유해도 되지만 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안 된다.
  • ✔️영속성 컨텍스트
    • 영속성 컨텍스트란 엔티티를 영구 저장하는 환경을 말한다.
    • persist() 메서드는 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장한다.
    • 엄밀히 따지면 persist() 메서드를 사용하는 순간에 DB에 저장되는 것이 아니고 이 persist() 메서드를 사용하는 순간 영속성 컨텍스트에서 관리되는 대상이 되는 것이다.
    • 트랜잭션이 커밋되는 시점에 DB에 저장된다.
  • ✔️엔티티의 생명주기
    • 엔티티에는 다음과 같이 4가지 상태가 존재한다.
      • 비영속(new/transient) : 영속성 컨텍스트와 관계가 없는 상태
      • 영속(managed) : 영속성 컨텍스트에 저장된 상태
      • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
      • 삭제(removed) : 삭제된 상태

🙄영속성 컨텍스트2

  • ✔️엔티티 조회
    • 1차 캐시에 회원 엔티티를 저장한다.
    • 회원 엔티티는 아직 데이터베이스에 저장되지 않았다.
    • 1차 캐시의 키는 식별자(@Id) 값이다.
    • 식별자 값은 데이터베이스 기본 키와 매핑되어 있다.
    • 따라서 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스의 기본 키가 된다.
  • ✔️1차 캐시에서 조회
    • 찾고자하는 엔티티가 영속성 컨텍스트 내부의 1차 캐시에 존재한다면 데이터베이스를 조회하지 않고 메모리에 있는 1차 캐시에서 엔티티를 조회한다.
    • 1차 캐시에 찾는 엔티티가 존재하기 때문에 2번이 아닌 1번이 호출되는 것이다.
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction ta = em.getTransaction();
        ta.begin();

        try {
        	// ✔️처음 조회할 때 DB에서 해당 엔티티를 찾아서 1차 캐시에 저장하므로 SELECT 쿼리가 한 번 호출됨
            Member findMember1 = em.find(Member.class, 100L);
            // ✔️1차 캐시에 있기 때문에 DB에서 찾을 필요없이 1차 캐시에서 찾으면 됨
            Member findMember2 = em.find(Member.class, 100L);
            ta.commit();
        } catch (Exception e) {
            ta.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

실행 결과

Hibernate: 
    select
        m1_0.id,
        m1_0.name 
    from
        Member m1_0 
    where
        m1_0.id=?
  • ✔️데이터베이스에서 조회
    • 엔티티가 1차 캐시에 없으면 엔티티 매니저는 데이터베이스를 조회해서 엔티티를 생성한다. 1차 캐시에 저장한 후 영속 상태의 엔티티를 반환한다.
  • ✔️영속 엔티티의 동일성 보장
    • 동일성은 실제 같은 참조 값을 가지는 인스턴스의 경우 참을 반환하고 동등성은 참조하는 값은 다른데 인스턴스가 가지는 값이 같은 경우 참을 반환한다. 자바에서 동등성 비교를 위해서 equals() 메서드를 구현해야 한다.
    • 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환한다. 따라서 둘은 같은 인스턴스고 결과는 참이다.
  • ✔️엔티티 등록 - 트랜잭션을 지원하는 쓰기 지연
    • 회원A 영속화 → 영속성 컨텍스트는 1차 캐시에 회원 엔티티를 저장하면서 동시에 회원 엔티티 정보로 등록 쿼리 생성 → 등록 쿼리를 쓰기 지연 SQL 저장소에 보관
    • 회원B 영속화 → 영속성 컨텍스트는 1차 캐시에 회원 엔티티를 저장하면서 동시에 회원 엔티티 정보로 등록 쿼리 생성 → 등록 쿼리를 쓰기 지연 SQL 저장소에 보관
    • 트랜잭션 커밋 → 엔티티 매니저는 영속성 컨텍스트를 플러시
      • 플러시는 영속성 컨텍스트 변경 내용을 데이터베이스에 동기화하는 작업으로 등록, 수정, 삭제한 엔티티를 데이터베이스에 반영
    • 데이터베이스 동기화 작업이 이루어진 후, 실제 데이터베이스 트랜잭션 커밋

  • ✔️엔티티 수정의 변경 감지
    • JPA로 엔티티를 수정할 때는 단순히 엔티티를 조회해서 데이터만 변경하면 된다.
    • update() 메서드를 실행하지않아도 이것이 가능한데 이렇게 엔티티 변경사항을 데이터베이스에 자동으로 반영하는 기능을 변경 감지라고 한다.
    • JPA는 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 복사해서 저장해두는데 이것을 스냅샷이라고 한다. 그리고 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾는다.
    • 트랜잭션을 커밋하면 엔티티 매니저 내부에서 플러시가 호출된다.
    • 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾는다.
    • 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
    • 쓰기 지연 SQL 저장소의 SQL을 DB에 보낸다.
    • 데이터베이스 트랜잭션을 커밋한다.

🙄플러시

정의 : 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것

  • ✔️플러시
    • 영속성 컨텍스트 내용을 비우지 않음
    • 영속성 컨텍스트 변경 내용을 데이터베이스에 동기화
    • 트랜잭션이라는 작업 단위가 중요 → 커밋 직전에만 동기화
  • ✔️플러시 발생
    • 변경 감지
    • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
    • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.
  • ✔️영속성 컨텍스트 플러시 방법 - flush() 직접 호출
    • 실행 결과를 보면 선이 출력되기 전에 INSERT쿼리가 날아갔다. Member 객체를 영속성 컨텍스트에 보관하고 쓰기 지연 SQL 저장소에 1건이 저장되는데 그 다음 바로 flush() 메서드를 호출해서 데이터베이스에 바로 반영을 한 것이다.
    • flush() 메서드를 호출한다고 해서 1차 캐시에 있는 내용들이 삭제되는 것이 아니다.
public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        EntityTransaction ta = em.getTransaction();
        ta.begin();

        try{
            Member member = new Member(200L, "member200");
            em.persist(member);
            em.flush();
            System.out.println("======================");

            ta.commit();
        } catch (Exception e) {
            ta.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

실행 결과

Hibernate: 
    /* insert for
        org.example.Member */insert 
    into
        Member (name, id) 
    values
        (?, ?)
======================
  • ✔️영속성 컨텍스트 플러시 방법 - 트랜잭션 커밋 시 플러시 자동 호출
    • JPA는 트랜잭션을 커밋할 때 플러시를 자동으로 호출한다.
  • ✔️영속성 컨텍스트 플러시 방법 - JPQL 쿼리 실행 시 플러시 자동 호출
    • 영속성 컨텍스트에는 있지만 데이터베이스에 반영되지 않은 상태(Ex. persist())에서 JPQL을 실행하면 JPQL이 SQL로 변환되어 데이터베이스에 엔티티를 조회한다. 하지만 데이터베이스에 반영하지 않으면 쿼리 결과로 조회되는 결과가 없게 된다. 쿼리를 실행하기 전에 영속성 컨텍스트를 플러시해서 변경 내용을 데이터베이스에 반영해야 한다. JPA는 JPQL을 실행할 때도 플러시를 자동으로 호출한다.
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

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

🙄준영속 상태

  • ✔️준영속 상태
    • 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 것
  • ✔️준영속 상태로 만드는 방법
    • em.detach() : 특정 엔티티만 준영속 상태로 전환
    • em.clear() : 영속성 컨텍스트 완전히 초기화
    • em.close() : 영속성 컨텍스트 종료
  • ✔️detach()
    • 메서드를 호출하는 순간 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 엔티티를 관리하기 위한 모든 정보가 삭제된다.

0개의 댓글