Post

영속성 컨텍스트, EntityManager 관련 메서드

영속성 컨텍스트, EntityManager 관련 메서드

📌 개요

EntityManager 관련 메서드를 알아보자.

find 메서드를 제외한 대부분의 메서드는 트랜잭션 범위 내에서 수행되어야 한다.

📌 persist

persist 는 새로운 엔티티(비영속 상태)를 영속성 컨텍스트의 1차 캐시에 저장하여 영속 상태 엔티티로 만드는 메서드이다. DB에 엔티티를 저장하는 메서드가 아니라 영속성 컨텍스트에 잠시 보관하는 메서드이다. 즉, persist 메서드 호출 시점에는 실제 INSERT 쿼리가 발생하지 않는다. 다만, 영속성 컨텍스트의 쓰기 지연 SQL 저장소에 INSERT 쿼리가 보관된다.

쓰기 지연 SQL 저장소에 보관된 쿼리는 다음 작업이 수행될 때 실제로 flush된다.

  1. 트랜잭션이 커밋될 때
  2. flush 메서드가 호출될 때
  3. JPQL 쿼리를 실행하기 전 - FlushModeType.AUTO가 설정된 경우 데이터 일관성을 위해 자동으로 flush된다.

persist 로 전달된 엔티티 자체가 영속 상태가 되며, 이는 merge 메서드와 차이점을 가진다.

@GeneratedValue 어노테이션이 붙은 필드가 있는 경우 원본 객체에 ID를 직접 할당한다. 이를 통해 엔티티가 영속성 컨텍스트에서 식별 가능하게 된다.

연관 엔티티에 Cascade.PERSIST 옵션이 설정되었다면 연관 엔티티 또한 영속화된다.

📌 merge

merge 는 준영속 또는 비영속 상태 엔티티를 영속 상태로 만드는 메서드이다. persist 메서드와 달리 DB에 존재하는 엔티티를 대상으로 사용해야 한다.

먼저 엔티티의 식별자를 통해 1차 캐시에서 동일한 엔티티를 찾는다. 만약 1차 캐시에 없다면 DB에서 해당 엔티티를 조회하여 1차 캐시에 저장한다. 파라미터로 넘어온 엔티티의 모든 값을 복사하여 조회한 엔티티에 넘겨주고, 새로운 영속 상태 엔티티를 리턴한다. 즉, 파라미터로 넘어온 엔티티는 그대로 준영속 상태이며, 새로운 영속 상태 엔티티를 리턴하는 것이다. 따라서 merge 메서드를 사용하게 되면 반드시 리턴된 엔티티를 사용해야 한다.

주의해야 할 점은 merge 메서드를 수행하면 파라미터로 넘어온 엔티티의 모든 값을 복사하여 영속 상태 엔티티를 만든다는 점이다. 이는 원하는 속성만 변경할 수 있는 dirty checking 과 차이를 보인다. 또한 파라미터 엔티티에서 값이 설정되지 않는 필드는 리턴된 엔티티의 필드에서 null 로 설정된다.

📌 remove

remove 는 엔티티를 영속성 컨텍스트에서 제거하고 트랜잭션이 커밋되는 시점에 DB에서 실제로 엔티티를 삭제하는 연산을 수행하는 메서드이다. 따라서 remove 메서드는 반드시 영속 상태 엔티티를 대상으로 수행되어야 한다. remove 메서드 또한 호출 시점에 실제 DELETE 쿼리가 DB에 날라가는 것이 아니라 쓰기 지연 SQL 저장소에 DELETE 쿼리가 보관된 후 DB로 날라가게 된다.

연관 엔티티가 CascadeType.REMOVE 옵션이 붙은 경우 연관 엔티티 또한 같이 삭제된다.

📌 find

find 는 엔티티와 PK를 기반으로 DB에서 엔티티를 조회하고 영속 상태로 만드는 메서드이다. 만약 DB에 존재하지 않는다면 null 을 리턴한다.

호출 시점에 영속성 컨텍스트에 해당 엔티티가 이미 존재하면 DB에 접근하지 않고 1차 캐시의 엔티티를 즉시 리턴한다. 1차 캐시가 없다면 DB에 SELECT 쿼리를 날려 1차 캐시에 저장한 후 리턴한다.

find 메서드는 트랜잭션 범위 내에서 호출되어야 하며 트랜잭션 내에서는 리턴된 엔티티는 영속 상태이다. 단, 트랜잭션이 종료되면 엔티티는 준영속 상태가 된다.

만약 연관 엔티티 로딩 전략이 Lazy Loading 이라면 자식 엔티티를 조회할 때 추가 쿼리가 발생하게 되고, N+1 문제가 발생할 수 있다. 또한 여러 엔티티를 한 번에 조회하는 경우 createQuery 등으로 일괄적으로 조회하는 것이 더 효율적이다. find 메서드를 반복적으로 사용하면 N번의 데이터베이스 통신이 발생하기 때문이다.

클라이언트가 서버에 요청을 보내고 그에 대한 응답을 받기까지의 전체 과정으로 Round Trip 이라고 한다.

📌 getReference

getReferenceLazy Loading 으로 엔티티를 조회하는 메서드이다. ID 필드만 설정된 프록시 객체를 리턴하며, 실제 필드에 접근할 때 SELECT 쿼리가 발생한다. 프록시 객체 또한 1차 캐시에 저장된다.

📌 flush

flush 는 영속성 컨텍스트에 저장된 엔티티를 DB에 동기화하는 메서드이다. 즉, flush 메서드를 통해 영속성 컨텍스트와 DB 간 일관성을 보장할 수 있다.

이전에 언급된 것 처럼, flush는 flush 메서드를 명시적으로 호출할 때, 트랜잭션이 커밋될 때, JPQL 쿼리를 실행하기 전에 수행된다.

FlushModeType 은 두 가지 모드가 존재한다. AUTO 는 위 세 가지 조건 시 자동으로 flush된다. COMMIT 은 트랜잭선 커밋 시에만 flush된다.

📌 lock

lock 은 영속성 컨텍스트의 엔티티에 대해 락을 설정하는 메서드이다.

실제 동작 방식은 락 모드인 LockModeType 에 따라 달라진다.

OPTIMISTIC , OPTIMISTIC_FORCE_INCREMENT 로 설정되면 낙관적 락으로, @Version 필드를 기반으로 트랜잭선 커밋 또는 플러시 시점에 버전 불일치 여부를 확인한다. OPTIMISTIC_FORCE_INCREMENT 는 조회 시점에도 버전을 즉시 증가시킨다.

PESSIMISTIC_READ, PESSIMISTIC_WRITE, PESSIMISTIC_FORCE_INCREMENT 는 비관적 락으로, 즉시 락을 획득하여 다른 트랜잭션 간 충돌을 방지한다. PESSIMISTIC_READ 는 공유 락으로 읽기는 허용되나 수정, 삭제는 금지된다. PESSIMISTIC_WRITE 는 배타 락으로 읽기, 수정, 삭제 모두 금지된다. PESSIMISTIC_FORCE_INCREMENTPESSIMISTIC_WRITE 와 동일하나 버전 필드도 증가시킨다.

트랜잭션 밖에서 락을 초기화하면 예외가 발생할 수 있으며, 락 해제는 트랜잭션 커밋 또는 롤백 시 자동으로 이루어진다.

📌 refresh

refresh 는 영속성 컨텍스트로 관리되는 엔티티의 상태를 DB의 최신 상태로 업데이트하는 메서드이다. 영속성 컨텍스트에서 엔티티에 적용된 모든 변경 사항을 무시하고 DB에서 최신 데이터를 다시 로드하여 엔티티를 갱신한다.

연관 엔티티에 CascadeType.REFRESH 옵션이 설정되었다면 연관 엔티티 또한 새로고침된다.

📌 clear

clear 는 영속성 컨텍스트를 초기화하여 영속 상태 엔티티를 모두 준영속 상태로 전환하는 메서드이다. 아직 flush되지 않은 변경 사항은 모두 폐기된다.

📌 detach

detach 는 특성 영속 상태 엔티티를 준영속 상태로 전환하는 메서드이다. 연관 엔티티에 CascadeType.DETACH 가 지정된 경우 자식 엔티티 또한 준영속 상태로 전환된다.

다만 대량의 엔티티를 분리해야 하는 경우 detach 메서드를 반복적으로 호출하는 것보다 clear 메서드나 영속성 컨텍스트를 교체하는 것을 권장한다.

📌 contains

contains 는 주어진 엔티티가 영속성 컨텍스트에 의해 관리되는지 확인하는 메서드이다. 즉 준영속 및 비영속 상태 엔티티라면 contains 메서드의 리턴 값은 false 이다.

단, contains 메서드의 결과가 엔티티가 DB에 있는지를 대변하지 않으므로, DB에 엔티티가 있는지 확인하기 위해서는 find 또는 existsById 와 같은 메서드를 사용해야 한다.

📌 createQuery

createQuery 는 동적 JPQL 쿼리를 생성하여 실행하기 위한 메서드이다.

This post is licensed under CC BY 4.0 by the author.