조금 평범한 개발 이야기

JPA 저장과 영속화 과정에 대해서 본문

개발/쉽게 이해하고 사용하는 JPA

JPA 저장과 영속화 과정에 대해서

jogeum 2021. 7. 28. 02:35

개요

mybatis 와 같은 query template 엔진이 아니라 JPA 와 같은 ORM 을 사용하다 보면 기대하는 영속화 (DB 에 값을 쓰는) 시점과 실제 영속화 시점이 달라 당황 할 수 있습니다.

이것은 JPA 가 값 (Entity) 을 관리하는 방식이 조금 차이가 있기 때문 입니다.

이 문서에서는 JPA 에서 영속화 하는 시점에 대해서 설명하고 있습니다.


영속성 컨텍스트(Persistence Context)

JPA 의 영속화에 대해 알아보기 위해선 가장 먼저 영속성 컨텍스트에 대해 이해해야 합니다.

자바는 OOP 개념을 가지고 데이터를 객체처럼 관리 하고 데이터베이스(RDB) 는 관계형으로 데이터를 관리 합니다. 그래서 이 간극을 매우기 위해 ORM 이라는 개념이 등장 했습니다.

ORM 에서는 영속성 컨텍스트 라는 일종의 논리적인 임시 저장소 공간을 통해 관계형 데이터를 객체 (Object) 로 맵핑해 관리 합니다.

영속성 컨텍스트는 몇가지 특징을 가지는데 이에 대해서 알아 보겠습니다.


데이터 캐싱

영속성 컨텍스트는 1차 캐시 역할을 해주기 때문에 id 가 'A' 인 User 엔티티를 조회 할때 동일한 트랜잭션 안에서는 항상 같은 객체를 반환해 줍니다.

@Transactional
public void test() {
    var user1 = userRepository.findById("A");
    var user2 = userRepository.findById("A");
    // user1 와 user2 는 동일한 객체 입니다.
}

변경 감지 (dirty checking)

영속성 컨텍스트에서 관리되는 엔티티는 값이 변경이 되었을 경우 이를 자동으로 인지해 데이터베이스에 영속화를 진행 합니다.

영속화를 진행할때 jpa 가 update 쿼리를 생성해 쿼리 저장소에 추가 하며, 이것은 즉시 반영은 되지 않고 트랜잭션이 끝나는 시점에 실제 영속화 과정이 완료 됩니다.

그렇기 때문에 변경 감지된 대상 엔티티는 명시적으로 repository.save 함수를 호출할 필요가 없습니다.

@Transactional
public void test() {
    var user1 = userRepository.findById("A");
    // repository 를 통해 조회해온 엔티티는 영속성 컨택스트에서 관리 됩니다.
    user1.setName("조금");
    // user1 에 값이 변경되면 자동으로 인지해 변경 사항을 반영 (update) 합니다.
    // userRepository.save(user1); save 함수를 호출할 필요가 없습니다.
}

트랜잭션 쓰기 지연

영속성 컨텍스트는 트랜잭션 범위 안에서만 동작이 되며 모든 엔티티의 생성/수정/삭제에 해당하는 쿼리를 쌓아 두었다가 트랜잭션 끝나는 시점(commit)에 일괄 / 순차적으로 반영 합니다.

영속성 컨텍스트에서 관리되는 엔티티는 트랜잭션 단위로 관리되며 트랜잭션이 끝나는 순간 (commit / rollback) 캐싱된 데이터는 초기화 됩니다.


신규 엔티티 영속화

JPA 에서는 모든 엔티티를 영속성 컨텍스트에서 관리 하지만 엔티티를 신규로 생성 했다면 해당 엔티티는 아직 관리 대상이 아니기 때문에 repository 를 통해 영속화를 시키겠다고 명시를 해줘야 합니다.

@Transactional
public void test() {
    var user1 = User.builder().id("A").name("조금").build(); //new User("A", "조금");
    userRepository.save(user1); 
    // 새롭게 생성된 엔티티는 아직 관리 대상이 아니기 때문에 save 를 명시해 줘야 합니다.
    // new User(); 로 단지 엔티티만 새롭게 만들어 놓으면 영속화가 되지 않습니다.
}

플러시 flush

플러시는 쓰기 지연된 쿼리 저장소에 쿼리를 쌓지 않고 데이터베이스에 바로 실행 합니다. 하지만 영속성 컨텍스트는 트랜잭션 범위 안에서만 동작이 되기 때문에 쿼리가 실행 된다고 하더라도 실제 영속화는 일어나지 않은 상태 입니다.

즉 플러시는 쿼리 저장소에 쌓인 쿼리들을 한번에 실행 / 비운다는 것이지 이를 영속화 한다는 것은 아닙니다. 플러시 이후에 실행되는 쿼리는 다시 차곡 차곡 쌓이게 됩니다.

@Transactional
public void test() {
    var user1 = User.builder().id("A").name("가").build(); 
    userRepository.saveAndFlush(user1); 
    // saveAndFlush 는 쿼리 저장소에 쿼리를 쌓지 않고 바로 쿼리를 던집니다.

    var user2 = User.builder().id("B").name("나").build(); 
    userRepository.save(user2); 
    entityManager.flush();
    // entityManager 직접 주입 받아 flush 를 할 수도 있습니다. 이때 쌓인 모든 쿼리를 실행/비웁니다.

    var user3 = User.builder().id("C").name("다").build(); 
    userRepository.save(user3); 
    // 정상적으로 트랜잭션이 끝나면 쿼리 저장소에 남아 있는 쿼리는 순차적으로 실행 됩니다.
}

정리

  • JPA 엔티티를 영속성 컨텍스트에서 관리 한며 엔티티 값이 변경되면 알아서 반영해 준다.
  • 영속성 컨텍스트는 트랜잭션 단위에서만 동작 한다.
  • 신규 엔티티는 아직 관리 대상이 아니므로 save 를 명시해 준다.
  • 플러시는 실제 영속화하는 과정이 아니라 지연된 쿼리들을 한번에 실행만 시키는 과정이다.
Comments