Hayden's Archive
[JPA] 자바 ORM 표준 JPA 프로그래밍 3장 본문
- JPA가 제공하는 기능
- 설계 : 엔티티와 테이블을 매핑
- 매핑한 엔티티를 실제 사용
- EntityManagerFactory & EntityManager
- EntityManagerFactory
- 데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 EntityManagerFactory를 하나만 생성
- EntityManager
- 엔티티를 관리하는 관리자
- 엔티티를 저장/수정/삭제/조회 등 엔티티와 관련된 모든 일을 함
- 참고) EntityManagerFactory와 EntityManager의 생성 관련 내용
- EntityManager는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않음
- 보통 트랜잭션을 시작할 때 커넥션 획득
- 하이버네이트를 포함한 JPA 구현체들은 EntityManagerFactory를 생성할 때 커넥션풀도 만듦
- J2EE 환경(스프링 프레임워크 포함)에서 사용하면 해당 컨테이너가 제공하는 데이터소스 사용
- EntityManagerFactory
- 영속성 컨텍스트 persistence context
- 영속성 컨텍스트란?
- 엔티티를 영구 저장하는 환경
- EntityManager를 생성할 때 하나 만들어짐
- 자바를 직접 다루는 J2EE 환경에서는 EntityManager를 만들면 그 내부에 영속성 컨텍스트도 함께 만들어짐
- EntityManager를 통해서 영속성 컨텍스트에 접근할 수 있고 영속성 컨텍스트를 관리할 수 있음
- EntityManager로 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리함
- 엔티티의 생명주기
- 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
- 엔티티 객체를 생성했을 때 아직 저장하지 않은 순수한 객체 상태
- 영속성 컨텍스트나 데이터베이스와는 전혀 관련이 없는 순수한 객체 상태
- 영속(managed) : 영속성 컨텍스트에 저장된 상태
- 영속성 컨텍스트에 의해 관리됨
- persist() : EntityManager를 통해 엔티티를 영속성 컨텍스트에 저장한 상태
- find(), JPQL을 사용해서 조회한 엔티티도 영속성 컨텍스트가 관리
- 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
- detach()로 영속성 컨텍스트가 관리하던 엔티티를 영속성 컨텍스트가 관리하지 않게 함
- close()로 영속성 컨텍스트를 닫음
- clear()로 영속성 컨텍스트를 초기화함
- 삭제(removed) : 삭제된 상태
- remove()를 호출하는 순간 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제
- 이렇게 삭제된 엔티티는 재사용하지 말고 자연스럽게 가비지 컬렉션의 대상이 되도록 두는 게 좋음
- 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
- 영속성 컨텍스트란?
- 영속성 컨텍스트의 특징
- 영속성 컨텍스트는 엔티티를 식별자 값(@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하는 동적 생성 쿼리
- @DynamicUpdate : 수정된 데이터만 사용해서 동적으로 UPDATE하는 동적 수정 쿼리
- 지연 로딩
- 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법
- 1차 캐시 (성능상 이점)
- 영속성 컨텍스트는 엔티티를 식별자 값(@Id)으로 구분
- 플러시
- flush : 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
- 플러시 실행시
- 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교
- 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송
- 영속성 컨텍스트를 플러시하는 방법
- 직접 호출
- em.flush()를 직접 호출. 거의 사용하지 않음.
- 트랜잭션 커밋 시 플러시 자동 호출
- JPQL 쿼리 실행 시 플러시 자동 호출
- JPQL이나 Criteria 같은 객체지향 쿼리를 호출할 때 플러시 실행됨
- 예컨대 앞선 코드에서 persist()만 하고 커밋하지 않은 경우 데이터베이스에는 반영되지 않았으므로 JPQL 실행시 쿼리 결과로 조회되지 않음 → 이런 문제를 예방하기 위해 JPQL 실행시 flush()를 먼저 자동 호출
- find() 메서드 호출시에는 flush() 실행 안 됨
- 직접 호출
- 플러시 모드 옵션
- FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시 (default)
- FlushModeType.COMMIT : 커밋할 때만 플러시
- 준영속
- 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리됨
- 영속성 컨텍스트가 제공하는 기능 사용X
- 영속 상태의 엔티티 → 준영속 상태의 엔티티
- detach()
- 특정 엔티티만 준영속 상태로 전환
- clear()
- 영속성 컨텍스트를 완전히 초기화해서 모든 엔티티를 준영속 상태로 만듦
- 영속성 컨텍스트를 제거하고 새로 만든 것과 같음
- close()
- 영속성 컨텍스트를 종료
- detach()
- 준영속 상태의 특징
- 비영속 상태처럼 영속성 컨텍스트의 기능 사용X
- 비영속 상태는 식별자 값이 없을 수도 있지만 준영속 상태는 반드시 식별자 값은 가지고 있음
- 준영속 상태는 영속성 컨텍스트가 관리하지 않아서 지연 로딩시 문제 발생
- 병합 merge()
- 준영속 상태의 엔티티를 받아서 그 정보로 새로운 영속 상태의 엔티티를 반환
- 파라미터로 넘오온 엔티티는 병합 후에도 준영속 상태로 남아있음
- contains() : 영속성 컨텍스트가 파라미터로 넘어온 엔티티를 관리하는지 확인하는 메서드(응답값 boolean)
- 비영속 병합
- merge()는 비영속 엔티티도 영속 상태로 만들 수 있음
- 병합은 준영속, 비영속을 신경쓰지 않음
- 파라미터로 넘어온 엔티티의 식별자 값으로 영속성 컨텍스트를 조회하고 찾는 엔티티가 없으면 데이터베이스에서 조회해서 병합(데이터베이스에서도 없을 경우 새로운 엔티티를 생성해서 병합)
- 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리됨