-
엔티티 매니저 팩토리 : 생성하는데 비용이 매우 크다. 그리고 여러 쓰레드가 동시에 접근해도 안전하므로 서로 스레드 간에 공유가 가능하여 보통 어플리케이션 내에 하나만 생성하여 사용한다.
-
엔티티 매니저 : 엔티티 매니저 팩토리에서 생성되고 생성하는 비용이 거의 들지 않는다. 여러 쓰레드가 동시에 접근하면 동시성 문제가 발생하므로 쓰레드 간에 절대 공유하면 안된다.
- 영속성 컨텍스트 : 엔티티를 영구 저장하는 환경. 엔티티 매니저를 생성할 때 하나 만들어지며 엔티티 매니저를 통해 접근하고 관리되어진다.
- 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계X
- 영속(managed) : 영속성 컨텍스트에 저장된 상태
- 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(removed) : 삭제된
=> 책의 그림을 보면 좀 더 이해하기 쉬우실 것 입니다!
- 영속성 컨텍스트와 식별자 값
- 영속성 컨텍스트와 데이터베이스 저장
- 영속성 컨텍스트가 엔티티를 관리시 얻어지는 장점들
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
-
영속성 컨테스트 내부에는 캐시가 존재 -> 1차 캐시
-
영속 상태 엔티티 모두 1차 캐시에 저장
-
내부에 Map이 하나 존재,
@Id
로 매핑한 식별자가 Key, value가 엔티티 인스턴스 -
em.find() -> 1차 캐시에서 엔티티 조회 -> 없없을 경우 DB에서 조회
-
DB에서 조회시 조회하여 엔티티 생성하여 1차 캐시에 저장 -> 영속 상태의 엔티티 반환
-
1차 캐시에 있을 경우 DB까지 갈 필요가 없기에 성능상 이점!
-
1차 캐시를 사용하기에 반복해서 조회해도 1차 캐시에 있는 같은 엔티티 인스턴스 반환! => 동일성 보장
-
참고 : 동일성 vs 동등성
-
참고 : JPA는 1차 캐시를 통해 어플리케이션 차원에서 Repeatable Read 등급의 트랜잭션 격리 수준 제공
REPEATABLE READ : 반복해서 read operation을 수행하더라도 읽어 들이는 값이 변화하지 않는 정도의 isolation을 보장하는 level
Ref : https://suhwan.dev/2019/06/09/transaction-isolation-level-and-lock/
- 트랜잭션 커밋하기 SQL을 쓰기 지연 SQL 저장소에 저장 -> 트랜잭션을 지원하는 쓰기 지연
- 커밋 시에 영속성 컨텍스트를 플러시.
- 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업
- 즉 쓰기 지연 SQL 저장소에 모인 쿼리를 DB에 보내서 영속성 컨텍스트의 변경 내용을 DB에 동기화 한후에 실제 DB 트랜잭션을 커밋하는 방식
- 요구사항이 늘어나고 변경되며 수정 쿼리가 점점 추가되며 비즈니스 로직이 SQL에 의존되게 되는 문제가 발생
- JPA는 이를 해결! -> update 메서드 존재 X
- 변경 감지(dirty checking)를 사용
- 트랜잭션을 커밋시 엔티티 매니저가 flush()
- 엔티티와 스냅샷(영속성 컨텍스트에 보관한 엔티티의 최초의 상태) 비교 -> 변경된 엔티티 찾음
- 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL저장소에 보냄
- 쓰기 지연 저장소의 SQL을 DB로
- DB 트랜잭션 커밋
- 추가 사항 : JPA는 변경된 부분만 UPDATE 쿼리를 날리는 것이 아닌 엔티티의 모든 필드를
업데이트
- 모든 필드를 사용하면 수정쿼리가 항상 동일 -> 즉 어플리케이션 로딩 시점에 수정 쿼리를 미리 생성해주고 재사용
- DB에 동일한 쿼리를 보내면 DB는 이전에 한 번 파싱된 쿼리를 재사용 가능
- 물론 필드가 너무 많거나 저장되는 내용이 너무 크면 설정을 통해 수정된 데이터만 동적으로 UPDATE SQL 생성 가능
- 근데 사실 30개 정도 이상되면 보통 동적으로 해주는게 좋다는데 그 정도 되면 그냥 테이블이 이상하게 아닌가...
- INSERT SQL도 데이터가 존재하는(null X) 필드만으로 동적으로 생성하게 설정 가능
- remove()로 엔티티 넘겨줘서 엔티티 삭제
- 물론 바로 삭제가 아닌 SQL에 저장소에 등록되고 커밋지에 플러시 호출되서 위처럼~
- 이때 remove()를 호출하는 순간 해당 엔티티는 일단 영속성 컨텍스트에서 제거되므로 해당 엔티티 재사용 X -> 자연스럽게 GC의 대상이 되도록 두어라~
- 플러시는 영속성 컨텍스트의 변경 내용을 DB에 반영
- 변경 감지 동작 -> 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교 -> 수정된 엔티티 발견 -> 해당하는 수정 쿼리 만들어서 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송
- em.flush()
- 트랜잭션 커밋 시 플러시가 자동 호출
- JPQL 쿼리 실행시 플러시가 자동 호출 -> JPQL은 SQL로 변환되어 DB에서 조회 -> 그렇기에 그 전에 DB 적용 안된 사항이 있으면 결과가 이상해짐 -> 따라서 JPQL 쿼리 실행시 플러시 호출
FlushModeType.AUTO
: 커밋이나 쿼리를 실행할 때 플러시(기본값)FlushModeType.COMMIT
: 커밋할 때만 플러시
- 플러시라는 이름으로 인해 영속성 컨텍스트에 보관된 엔티티를 지운다고 생각 X
- 그냥 영속성 컨텍스트의 변경 내용을 DB에 동기화하는 것
- 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능 사용 X
- em.detach(entity): 특정 엔티티만 준영속 상태로 전환
- em.clear(): 영속성 컨텍스트를 완전히 초기화
- em.close(): 영속성 컨텍스트 종료
- 거의 비영속
- 식별자 값 가지고 있다.
- 지연 로딩 불가
- 준영속 상태의 엔티티를 받아서 그 정보로 새로운 영속 상태의 엔티티 반환
User merged = em.merge(user); // user는 준영속 그대로, merge는 영속
// 준영속 상태인 user는 이제 노쓸모
// 따라서 아래와 같이 쓰는 것이 좋음
user = em.merge(user);
- 병합은 파라미터로 넘어온 엔티티의 식별자 값으로 영속성 컨텍스트 조회
- 찾는 엔티티가 없으면 데이터 베이스 조회 -> 없으면 새로운 엔티티 생성해서 병합
- 병합은 사실 준영속, 비영속 신경X
- 식별자 값으로 엔티티 조회할 수 있으면 불러서 병합
- 없으면 새로 생성해서 병합
- 즉 save, update 기능을 수행