Hayden's Archive

[JPA] 자바 ORM 표준 JPA 프로그래밍 3장 본문

Study/Java & Kotlin

[JPA] 자바 ORM 표준 JPA 프로그래밍 3장

_hayden 2022. 4. 29. 16:28
  • JPA가 제공하는 기능
    • 설계 : 엔티티와 테이블을 매핑
    • 매핑한 엔티티를 실제 사용

 

  • EntityManagerFactory & EntityManager
    • EntityManagerFactory
      • 데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 EntityManagerFactory를 하나만 생성 
    • EntityManager 
      • 엔티티를 관리하는 관리자
      • 엔티티를 저장/수정/삭제/조회 등 엔티티와 관련된 모든 일을 함
    • 참고) EntityManagerFactory와 EntityManager의 생성 관련 내용
    • EntityManager는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않음
      • 보통 트랜잭션을 시작할 때 커넥션 획득
    • 하이버네이트를 포함한 JPA 구현체들은 EntityManagerFactory를 생성할 때 커넥션풀도 만듦
      • J2EE 환경(스프링 프레임워크 포함)에서 사용하면 해당 컨테이너가 제공하는 데이터소스 사용

 

  • 영속성 컨텍스트 persistence context
    • 영속성 컨텍스트란?
      • 엔티티를 영구 저장하는 환경
      • EntityManager를 생성할 때 하나 만들어짐
        • 자바를 직접 다루는 J2EE 환경에서는 EntityManager를 만들면 그 내부에 영속성 컨텍스트도 함께 만들어짐
      • EntityManager를 통해서 영속성 컨텍스트에 접근할 수 있고 영속성 컨텍스트를 관리할 수 있음
      • EntityManager로 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리함
    • 엔티티의 생명주기
      • 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
        • 엔티티 객체를 생성했을 때 아직 저장하지 않은 순수한 객체 상태
        • 영속성 컨텍스트나 데이터베이스와는 전혀 관련이 없는 순수한 객체 상태
      • 영속(managed) : 영속성 컨텍스트에 저장된 상태
        • 영속성 컨텍스트에 의해 관리됨
        • persist() : EntityManager를 통해 엔티티를 영속성 컨텍스트에 저장한 상태
        • find(), JPQL을 사용해서 조회한 엔티티도 영속성 컨텍스트가 관리
      • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
        • detach()로 영속성 컨텍스트가 관리하던 엔티티를 영속성 컨텍스트가 관리하지 않게 함
        • close()로 영속성 컨텍스트를 닫음
        • clear()로 영속성 컨텍스트를 초기화함
      • 삭제(removed) : 삭제된 상태 
        • remove()를 호출하는 순간 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
        • 이렇게 삭제된 엔티티는 재사용하지 말고 자연스럽게 가비지 컬렉션의 대상이 되도록 두는 게 좋음

 

  • 영속성 컨텍스트의 특징
    • 영속성 컨텍스트는 엔티티를 식별자 값(@Id)으로 구분
      • 영속 상태는 식별자 값이 반드시 있어야 함
    • JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영(flush)
    • 영속성 컨텍스트가 엔티티를 관리할 때 얻는 장점
      • 1차 캐시 (성능상 이점)
        • 영속성 컨텍스트가 내부에 가지고 있는 캐시
        • 영속 상태의 엔티티는 모두 이곳에 저장됨
        • 1차 캐시의 키는 식별자 값(데이터베이스 기본 키와 매핑)
          • 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스 기본 키 값
        • find() 메서드를 호출하면 먼저 메모리에 있는 1차 캐시에서 엔티티를 찾고 만약 찾는 엔티티가 1차 캐시에 없으면 데이터베이스에서 조회함
      • 영속 엔티티의 동일성 보장
        • 영속성 컨텍트스는 성능상 이점과 엔티티의 동일성을 보장
        • 같은 키로 조회할 경우 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스 반환
        • 1차 캐시를 통해 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
      • 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
        • EntityManager는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 차곡차곡 모아둠
        • 트랜잭션을 커밋하면 EntityManager가 영속성 컨텍스트를 flush함
          • 플러시 : 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업
          • 쓰지 지연 SQL 저장소에 모인 쿼리를 데이터베이스에 보냄
          • 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화(flush)한 후에 실제 데이터베이스 트랜잭션을 커밋(commit)
        • 모아둔 등록 쿼리를 데이터베이스에 한 번에 전달해서 성능을 최적화할 수 있음
      • 변경 감지 dirty checking
        • 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능 (setter로 변경 가능. update() 메서드 따로 없음)
        • 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용됨
          • 비영속, 준영속 엔티티는 값을 변경해도 데이터베이스에 반영X
        • JPA로 엔티티를 수정할 때는 단순히 엔티티를 조회해서 데이터만 변경하면 됨
        • 스냅샷 : 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해둠
          • 트랜잭션 커밋 → flush() 호출 → 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾음 → 변경된 엔티티가 있을 경우 수정 쿼리 생성 후 쓰기 지연 SQL 저장소로 보냄 → 쓰기 지연 SQL 저장소의 SQL을 데이터베이스에 보냄 → 데이터베이스 트랜잭션 커밋
        • JPA 기본 전략 : 모든 필드를 사용해서 UPDATE하는 정적 수정 쿼리
          • @DynamicUpdate : 수정된 데이터만 사용해서 동적으로 UPDATE하는 동적 수정 쿼리
            • 컬럼이 대략 30개 이상이 되면 정적 수정 쿼리보다 동적 수정 쿼리가 더 빠름
            • 기본 전략을 사용하고, 최적화가 필요할 정도로 느리면 그때 전략 수정
          • @DynamicInsert : 데이터가 NULL이 아닌 필드만으로 INSERT하는 동적 생성 쿼리
      • 지연 로딩
        • 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법

 

  • 플러시
    • flush : 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
    • 플러시 실행시
      • 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교
      • 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록
      • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송
    • 영속성 컨텍스트를 플러시하는 방법
      • 직접 호출
        • em.flush()를 직접 호출. 거의 사용하지 않음.
      • 트랜잭션 커밋 시 플러시 자동 호출
      • JPQL 쿼리 실행 시 플러시 자동 호출
        • JPQL이나 Criteria 같은 객체지향 쿼리를 호출할 때 플러시 실행됨
        • 예컨대 앞선 코드에서 persist()만 하고 커밋하지 않은 경우 데이터베이스에는 반영되지 않았으므로 JPQL 실행시 쿼리 결과로 조회되지 않음 → 이런 문제를 예방하기 위해 JPQL 실행시 flush()를 먼저 자동 호출
        • find() 메서드 호출시에는 flush() 실행 안 됨
    • 플러시 모드 옵션
      • FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시 (default)
      • FlushModeType.COMMIT : 커밋할 때만 플러시

 

  • 준영속
    • 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리됨
      • 영속성 컨텍스트가 제공하는 기능 사용X
    • 영속 상태의 엔티티 → 준영속 상태의 엔티티
      • detach()
        • 특정 엔티티만 준영속 상태로 전환
      • clear() 
        • 영속성 컨텍스트를 완전히 초기화해서 모든 엔티티를 준영속 상태로 만듦
        • 영속성 컨텍스트를 제거하고 새로 만든 것과 같음
      • close() 
        • 영속성 컨텍스트를 종료
    •  준영속 상태의 특징
      • 비영속 상태처럼 영속성 컨텍스트의 기능 사용X
      • 비영속 상태는 식별자 값이 없을 수도 있지만 준영속 상태는 반드시 식별자 값은 가지고 있음 
      • 준영속 상태는 영속성 컨텍스트가 관리하지 않아서 지연 로딩시 문제 발생
    • 병합 merge()
      • 준영속 상태의 엔티티를 받아서 그 정보로 새로운 영속 상태의 엔티티를 반환
      • 파라미터로 넘오온 엔티티는 병합 후에도 준영속 상태로 남아있음
      • contains() : 영속성 컨텍스트가 파라미터로 넘어온 엔티티를 관리하는지 확인하는 메서드(응답값 boolean)
    • 비영속 병합
      • merge()는 비영속 엔티티도 영속 상태로 만들 수 있음
      • 병합은 준영속, 비영속을 신경쓰지 않음
      • 파라미터로 넘어온 엔티티의 식별자 값으로 영속성 컨텍스트를 조회하고 찾는 엔티티가 없으면 데이터베이스에서 조회해서 병합(데이터베이스에서도 없을 경우 새로운 엔티티를 생성해서 병합)