엔티티 매니저
- JPA가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계 부분과 매핑한 엔티티를 실제 사용하는 부분으로 나눌 수 있다.
- 엔티티 매니저는 엔티티를 저장, 수정, 삭제, 조회하는 등 엔티티와 관련된 모든 일을 처리한다.
엔티티 매니저 팩토리
- 엔티티 매니저 팩토리는 엔티티 매니저를 만드는 공장으로, 만드는 비용이 크기 때문에 데이터베이스 당 한 개만 만들어서 애플리케이션 전체에서 공유한다.
- 이후 필요할 때마다 엔티티 매니저 팩토리에서 엔티티 매니저를 생성해서 사용한다.
- 하이버네이트를 포함한 JPA 구현체들은 엔티티 매니저 팩토리를 생성할 때 커넥션 풀도 생성한다.
- 엔티티 매니저는 데이터베이스 연결이 꼭 필요한 시점(트랜잭션을 시작할 때)까지 커넥션을 얻지 않다가, 트랜잭션을 시작할 때 커넥션을 획득한다.
- 참고로 엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드간에 공유해도 되지만, 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 공유하면 안 된다.
엔티티의 생명주기
- 엔티티에는 다음과 같이 4가지 상태가 존재한다.
비영속 (New)
- 영속성 컨텍스트와 전혀 관계가 없는 상태
- 엔티티 객체를 생성했을 때의 순수한 객체 상태로, 영속성 컨텍스트나 데이터베이스와는 전혀 관계가 없다.
- 영속 상태의 엔티티를 준영속 상태로 만드는 방법은 다음과 같다.
em.detach(entity) | - 특정 엔티티만 준영속 상태로 전환한다. - 메서드를 호출하는 순간 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 엔티티와 관련된 모든 정보가 제거된다. |
em.clear() | - 영속성 컨텍스트를 완전히 초기화한다. - 해당 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만든다. |
em.close() | - 영속성 컨텍스트를 종료한다. - 해당 영속성 컨텍스트가 관리하던 모든 영속 상태의 엔티티가 모두 준영속 상태가 된다. |
영속 (Managed)
- 영속성 컨텍스트에 저장되어 관리되는 상태
- em.persist()를 통해서 엔티티를 영속성 컨텍스트에 저장하면 영속 상태가 된다.
- 또한 em.find()나 JPQL을 사용해서 조회한 엔티티도 영속 상태가 된다.
준영속 (Detached)
- 영속성 컨텍스트에 저장되었다가 분리된 상태
- 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다.
- 특정 엔티티를 준영속 상태로 만들려면 em.detach()를 호출하면 된다.
- 또한 em.close()를 호출해서 영속성 컨텍스트를 닫거나, em.clear()로 초기화해도 준영속 상태가 된다.
삭제 (Removed)
- em.remove()를 통해 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제할 수 있다.
영속성 컨텍스트
- 영속성 컨텍스트란 엔티티를 영구 저장하는 환경으로, 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
- 영속성 컨텍스트는 엔티티 매니저를 생성할 때 만들어지며, 엔티티 매니저를 통해서 영속성 컨텍스트에 접근/관리할 수 있다.
1차 캐시
- 영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라고 한다.
- 영속 상태의 엔티티는 모두 이곳에 저장되며, 영속성 컨텍스트 내부에 Map 형태로 존재한다.
- 키 - @Id로 매핑한 식별자
- 값 - 엔티티 인스턴스
- 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스의 기본키 값이다.
- em.find()를 호출하면 먼저 1차 캐시에서 엔티티를 찾고, 만약 찾는 엔티티가 1차 캐시에 없으면 데이터베이스에서 조회해서 엔티티를 생성한다.
- 이후 1차 캐시에 저장한 후 영속 상태의 엔티티를 반환한다.
동일성 보장
- em.find()를 반복해서 호출해도 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환하기 때문에 성능상 이점과 엔티티의 동일성을 보장한다.
동일성 | 실제 인스턴스가 같기 때문에 참조값을 비교하는 == 비교의 값이 같다. |
동등성 | 실제 인스턴스는 다를 수 있지만 인스턴스가 갖고 있는 값이 같다. (equals() 메서드로 구현) |
트랜잭션을 지원하는 쓰기 지연
- 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고, 내부 쿼리 저장소에 INSERT SQL을 모아둔다.
- 이후 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스에 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연이라고 한다.
- 영속성 컨텍스트는 1차 캐시에 회원 엔티티를 저장하면서 동시에 회원 엔티티 정보로 INSERT 쿼리를 만들고, 만들어진 쿼리를 쓰기 지연 SQL 저장소에 보관한다.
- 트랜잭션을 커밋하면 엔티티 매니저는 우선 영속성 컨텍스트를 플러시한다.
- 이때 쓰기 지연 SQL 저장소에 모인 쿼리들을 데이터베이스에 보내서 등록, 수정, 삭제한 엔티티를 데이터베이스에 반영한다.
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화한 후, 실제 데이터베이스 트랜잭션을 커밋한다.
변경 감지
- 수정 쿼리를 상황에 따라 계속해서 추가하면 수정 쿼리가 많아지고, 비즈니스 로직을 분석하기 위해 SQL을 계속 확인해야 하므로 SQL에 의존하게 된다.
- JPA로 엔티티를 수정할 때는 단순히 엔티티를 조회해서 데이터만 변경하면 되는데, 이처럼 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능을 변경 감지라고 한다.
- JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는데 이것을 스냅샷이라고 한다.
- 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾고, 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
- 따라서 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다.
- 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스 반영하는 것으로, JPA는 트랜잭션을 커밋할 때와 find()를 제외한 JPQL을 실행할 때 플러시를 자동 호출한다.
※ UPDATE 전략
- JPA의 기본 전략은 엔티티의 모든 필드를 업데이트한다.
- 이렇게 모든 필드를 사용하면 데이터베이스에 보내는 데이터 전송량이 증가하는 단점이 있지만, 수정 쿼리는 항상 같기 때문에 애플리케이션 로딩 시점에 미리 생성해둔 수정 쿼리를 재사용할 수 있다.
- 또한, 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 파싱해둔 쿼리를 재사용할 수 있다.
- 만약 필드가 많거나 저장되는 내용이 너무 클때는 @DynamicUpdate 어노테이션을 사용해서 수정된 데이터만 사용해서 동적으로 UPDATE SQL을 생성하는 전략을 선택할 수 있다.
지연 로딩
- 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법
'Spring > JPA' 카테고리의 다른 글
엔티티 매핑 (0) | 2022.03.25 |
---|---|
스프링에서의 영속성 관리 (0) | 2022.03.05 |
N+1 문제와 해결 방법 (0) | 2022.03.05 |
JPA와 ORM (0) | 2022.02.23 |
JPQL과 페치 조인 (0) | 2022.02.23 |