ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA 엔티티 매니저 팩토리와 엔티티 매니저, 영속성 컨텍스트
    Java/JPA 2022. 7. 31. 00:48
    반응형

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

    엔티티 매니저는 엔티티를 저장, 수정, 삭제, 조회등 엔티티와 관련된 모든 일을 처리.

    엔티티 매니저 팩토리를 생성하는 코드.

    EntityManagerFactory emf = 
            Persistence.createEntityManagerFactory("jpabook");

    생성한 엔티티 매니저 팩토리를 통해

    EntityManager em = emf.createEntityManager();

    엔티티 매니저를 생성한다.

    • 엔티티 매니저 팩토리
      • 엔티티 매니저 팩토리는 말그대로 엔티티 매니저를 만드는 공장임
      • 생성 비용이 큼
      • 한개만 만들어 어플리케이션 전체에서 공유하도록 설계 되어있음.
      • 여러 스레드가 동시에 접근해도 안전하게 설계
      • 커넥션 풀
        • J2SE -> 하이버네이트를 포함한 jpa 구현체들은 EntityManager를 생성할때 커넥션 풀도 같이 만듬.
        • J2EE -> (스프링 프레임워크) 해당 컨테이너가 제공하는 데이터 소스 사용
    • 엔티티 매니저
      • 생성 비용 적음.
      • 여러 스레드가 동시에 접근하면 동시성 문제가 발생 ( 공유 X )
      • DB 커넥션풀에서 연결이 꼭필요한 시점에 커넥션을 얻어 사용 ( 사용자 요청에 의해 트랜잭션을 시작할때)

      영속성 컨텍스트 ( Persistence context )

    • 엔티티를 영구 저장하는 환경
    • 엔티티 매니저로 엔티티를 저장하거나 조회하면
      엔티티 매니저는 영속성 컨텍스트에 1차로 엔티티를 보관하고 관리
    • 엔티티 매니저를 생성할 때 하나 만들어짐.
    • persist()메소드는 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장

    엔티티의 생명주기

    1. 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 상태
    2. 영속(managed): 영속성 컨텍스트에 저장된 상태
    3. 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
    4. 삭제(removed): 삭제된 상태

    1. 비영속

    엔티티 객체를 막 생성한 상태

    Member member = new member();
    member.setUsername("memberA");

    여기서 member는 비영속 상태

    2.영속

    엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장한 상태(영속성 컨텍스트가 관리)

    em.persist(member);

    3.준영속

    영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 더이상 관리하지 않는 상태

    em.detach(member); // Member 엔티티를 영속성 컨텍스트에서 분리 -> 준영속 상태
    em.close(); // 영속성 컨텍스트를 닫는다 -> Member 엔티티 준영속 상태
    em.clear(); // 영속성 컨텍스틀 초기화 -> Member 엔티티 준영속 상태

    4.삭제

    엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제.

    em.remove(member);

    영속성 컨텍스트의 특징

    • 영속성 컨텍스트와 식별자 값
      • 영속성 컨텍스트는 엔티티를 식별자 값으로 구분( @Id )
      • 즉 영속 상태는 식별자 값이 반드시 있어야함.
      • 영속성 컨텍스트와 데이터베이스 저장
        • 영속성 컨텍스트에 엔티티를 저장 (persist())
        •  

    보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 저장된 엔티티를 db에 반영 (이것을 flush라 함)

      •  
    • 영속성 컨텍스트가 엔티티를 관리하면 얻는 장점
      1. 1차 캐시
      2. 동일성 보장
      3. 트랜잭션을 지원하는 쓰기 지연
      4. 변경 감지
      5. 지연 로딩

    엔티티 조회

    • 연속성 컨텍스트는 내부에 캐시를 가지고 있다. (1차 캐시)
      영속 상태의 엔티티는 모두 이곳에 저장됨.

    ex) 영속성 컨텍스트 내부에 Map이 있고
    key@Id로 매핑한 식별자
    value엔티티 인스턴스

    // 엔티티 생성 (비영속)
    Member member = new Member();
    member.setId("member1");
    
    // 엔티티 영속
    em.persist(member);
    

    image

    1. 1차 캐시의 키는 식별자 값이다.
    2. 식별자 값은 데이터베이스 기본 키와 매핑되어 있다.

    즉 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스의 기본 키 값

    엔티티 조회

    Member member = em.find(Member.class, "member1");

    EntityManager.find() 메소드는 클래스 타입과 조회할 엔티티 식별자 값을 받는다.

    public <T> T find(Class<T> entityClass, Object primaryKey);

    1차 캐시에서 조회

    em.find()를 호출하면 먼저 1차 캐시에서 엔티티를 찾고 없으면 db 조회

    image

    데이터 베이스에서 조회

    1. em.find()를 호출했는데 찾는 엔티티가 1차 캐시에 없으면
    2. 엔티티 매니저는 db에서 조회해 엔티티를 생성
    3. 1차 캐시에 저장 ( 영속 상태 )
    4. 엔티티 반환

    image

    영속 엔티티의 동일성 보장

    Member a = em.find(Member.class, "member1");
    Member b = em.find(Member.class, "member1");
    
    System.out.println( a == b ); //  true

    em.find(Member.class, "member1");이 코드를 여러번 호출해도
    모두 영속성 컨텍스트 1차 캐시에 담겨있는 같은 엔티티 인스턴스를 반환
    성능상 이점과 엔티티의 동일성을 보장함.

    참고

    • 동일성 : 실제 인스턴스가 같다. 참조비교 ==비교의 값이 true
    • 동승성 : 실제 인스턴스는 다를 수 있다. 하지만 가지고 있는 값이 같다. equals()로 비교

    엔티티 등록

    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    // 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야함
    transaction.begin();
    
    em.persist(memberA);
    em.persist(memberB);
    // persist하면 영속성 컨텍스트에만 저장
    // db로 데이터를 보내지 않는다.
    
    // 커밋하는 순간 데이터베이스에 데이터 전송/저장(insert)
    transaction.commit();
      • 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 INSERT SQL을 담아둠
      • 트랜잭션 커밋시 모아둔 쿼리를 db에 보냄

    이것을 쓰기 지연(transactional write-behind)이라함( 트랜잭션이 지원 )


    엔티티 수정

    변경 감지

    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    transaction.begin();
    
    // 영속 엔티티 조회
    Member memberA = em.find(Member.class, "memberA");
    
    // 영속 엔티티 데이터 수정
    memberA.setUsername("h1");
    
    transaction.commit();
    • JPA로 엔티티를 수정할때
      엔티티 조회 -> 데이터 변경 -> 트랜잭션 커밋
      하면 알아서 update쿼리가 나간다.
    • 엔티티의 변경사항을 자동으로 감지해 데이터베이스에 반영한다
      이것을 변경 감지(dirty checking)이라 한다.
    • JPA는 엔티티를 영속성 컨텍스트에 보관할때
      최초 엔티티 상태를 복사해서 저장해둔다 ( 스냅샷 )
      그리고 플러시 시점에 스냅샷과 엔티티를 비교 변경된 엔티티를 찾아 db 에 반영한다.

    트랜잭션 커밋시 내부적으로 일어나는 일들을 보면

    1. 트랜잭션 커밋시 엔티티 매니저 내부에서 먼저 flush()가 호출됨
    2. 엔티티와 스냅샷 비교, 변경된 엔티티를 찾음
    3. 변경된 엔티티 존재시 수정 쿼리를 생성 -> 쓰기 지전 sql 저장소로 보냄
    4. 쓰기 지연 저장소의 sql을 db에 반영
    5. 트랜잭션 커밋

     

    변경감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용됨.

    JPA의 기본 update 전략은 엔티티의 모든 필드를 업데이트한다

    • 단정
      • 엔티티의 모든 필드를 업데이트 하기 때문에 db로 전송하는 데이터 전송량 증가
    • 장점
      • 모든 필드를 사용하면 수정 쿼리가 항상 같고 데이터만 다름
        즉 어플리케이션 로딩 시점에 수정쿼리 미리 생성 하고 재사용 가능
      • db에 동일한 쿼리를 날리면 db는 이전에 한 번 파싱된 쿼리 재사용 가능
    //수정된 데이터만 update
    @org.hibernate.anootaions.DynamicUpdate

    위 어노테이션을 사용하면 수정된 데이터만 사용해 동적으로 update sql을 생성


    엔티티 삭제

    엔티티를 삭제하려면 먼저 삭제 대상 엔티티를 조회해야함.

    Member memberA = em.find(Member.class, "memberA");
    em.remove(memberA);

    엔티티 등록과 비슷함

    1. em.remove(entity) -> 2.삭제 쿼리를 sql저장소에 등록

    -> 3. 트랜잭션 커밋(flush) -> 4. db에 삭제 쿼리 전달.

    em.remove(entity) 를 호출하는 순간 entity는 영속성 컨텍스트에서 제거됨.


    플러시

    플러시(flush)는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영함.

    flush()호출시 일어나는 일들

    1. 변경 감지가 동작
      1. 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교(수정된 엔티티를 찾음)
      2. 수정된 엔티티의 수정된 쿼리를 만들어 쓰기 지연 sql 저장소에 저장
    2. 쓰기 지연 sql 저장소의 쿼리를 db에 전달

    영속성 컨텍스트를 플러시 하는 방법

    1. em.flush()를 직접 호출
    2. 트랜잭션 커밋
    3. jpql쿼리 실행시

    1. 직접 호출

    • 엔티티 매니저의 flush() 메소드를 직접 호출
      영속성 컨텍스트를 강제로 플러시함.

    2. 트랜잭션 커밋 시 플러시 자동 호출

    • 트랜잭션을 커밋하기 전에 내부적으로 플러시를 호출해
      영속성컨텍스트의 변경 내용을 db에 반영

    3. JPQL 쿼리 실행 시 플러시 자동 호출

    • JPQL이나 Criteria같은 객체지향 쿼리를 호출할 때도 플러시가 실행됨.
      이유는 db에 쿼리를 날릴때 개발자는 저장 했지만 아직 실제 db에 반영되지 않고
      영속성 컨텍스트에만 저장되어있는 엔티티도 반영해서 조회해오기 위해 flush를 내부적으로 호출

    플러시 모드 옵션

    엔티티 매니저에 플러시 모드를 직접 지정하려면

    FlushModeType.AUTO -> 커밋이나 쿼리를 실행할 때 플러시 (default)
    FlushModeType.COMMIT -> 커밋할 때만 플러시


    준영속

    영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 것을 준영속 상태라 한다.

    영속 상태의 엔티티를 준영속 상태로 만드는 방법

    1. em.detach(entity) : 특정 엔티티만 준영속 상태로 전환.
      • 이 메소드를 호출하면 1차 캐시부터 쓰기지연 sql 저장소까지 해당 엔티티와 관련된 모든 정보가 삭제됨.
    2. em.clear() : 영속성 컨텍스트 초기화.
      • 영속성 컨텍스트를 초기화함, 즉 영속성 컨텍스트에 저장되어있는 모든 엔티티를 준영속 상태로 전환
    3. em.close() : 영속성 컨텍스트 종료

    준영속 상태의 특징

    비영속 상태에 가깝다.
      - 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않음(1차 캐시, 쓰기 지연, 변경 감지 etc...)

    식별자 값을 가지고 있다.

      - 이미 한번 영속 상태였으므로 반드시 식별자 값을 가지고 있음.

    지연로딩 불가.

      - 영속성 컨텍스트가 관리하지 않으므로 지연로딩시 문제 발생.

    병합 : merge()

    준영속 상태의 엔티티를 다시 영속 상태로 변경
    merge("준영속 상태의 엔티티")는 준영속 상태의 엔티티를 받아 새로운 영속 상태의 엔티티를 반환
    merge 호출시 파라미터로 넘어온 준영속 상태의 엔티티는 merge호출 이후에도 준영속 상태이다.
    즉 Entity entity = em.merge("entity1") 에서 entity 와 entity1은 서로다른 인스턴스인다.

    반응형
Designed by Tistory.