[Spring] JPA의 플러시(flush)

JPA를 사용할 때, 객체를 생성하고 이를 영속성 컨텍스트에 영속시켜 커밋하는 과정까지를 알아봤습니다. 그런데, JPA의 commit()을 호출할 때 항상 발생하는 flush()는 어떤 역할을 하는 녀석일까요?

 

 

flush()

커밋 메소드를 호출하게 되면 JPA 영속성 컨텍스트에 있는 객체들이 DB로 반영되게 됩니다. 그런데 실제로 commit() 메소드를 호출했을 때 이것이 진행되는 것일까요? 사실은 commit() 메소드가 호출될 때 flush() 메소드가 호출되어지고, 실제로는 flush() 메소드에 의해서 DB에 반영되어집니다.

즉, flush는 영속성 컨텍스트의 내용을 DB에 반영하는 역할을 하는 메소드이며, 쓰기 지연 SQL 저장소에 있는 SQL 쿼리가 DB 서버로 보내지기 때문에 영속성 컨텍스트는 그대로 남아 있게 됩니다. 따라서 반드시 객체별로 준영속(detached)상태로 전환해줘야 합니다.

// EntityManager 생성
EntityManager em = emf.createEntityManager();

// 영속 Entity 조회
Cafe cafe = em.find(Cafe.class, 1);

// 영속 Entity 수정
cafe.setName("iceAmericano");

// 이 때, DB 서버로 Query 전송
em.flush();

이후, commit() 메소드가 호출되었을 때는 DB에 영속성 컨텍스트들이 모두 저장되었음이 확정되어지게 되므로 이 때부터는 영속성 컨텍스트의 내용과 1차 캐시에 있던 객체들이 모두 제거됩니다.

 

 

flush() 동작 과정

commit은 DB의 Transaction commit 처럼 DB에 변경 사항 반영을 확정지었을 때 나타내는 메소드였고, 실제 쿼리를 전송하는 메소드는 별개로 존재한다는 것을 알았습니다. 그렇다면 flush 메소드는 어떤식으로 동작하는 걸까요?

  1. 변경을 감지한다. (Dirty Checking)
  2. 수정된 Entity를 쓰기 지연 SQL 저장소에 등록한다.
  3. 쓰기 지연 SQL 저장소의 Query를 DB에 전송한다. (INSERT, UPDATE, DELETE 등의 DML)
  4. flush() 메소드 호출 후, commit() 메소드를 호출한다.

다시 한 번 복습한다면, SELECT 쿼리와 같은 조회 쿼리는 EntityManager에서 바로 조회하고, 수정이나 추가 사항에 대해서만 영속성 컨텍스트에 넣고, 변경 사항을 쓰기 지연 SQL 저장소에 넣었다가 DB에 반영하는 방식이란 점을 알아둡시다.이러한 flush 메소드가 동작할 수 있는 이유는 DB Transaction이 있기 때문입니다. DB가 데이터를 쓸 때 작업 단위(Transaction)를 두고 처리하기 때문에 해당 Transaction의 끝점(commit) 직전에만 동기화를 해주면 되므로 중간 함수 호출이 가능한 것입니다. JPA에서 데이터의 싱크나 동시성에 관련된 것은 애플리케이션 레벨이 아닌 DB의 Transaction에 위임하게 되므로 애플리케이션을 개발할 때는 이러한 이슈는 신경쓰지 않으셔도 됩니다.

 

 

flush()는 언제 호출될까?

JPA에서 flush 메소드가 commit() 시점에 호출된다라고 한다면, 개발자가 굳이 flush()를 강제로 호출해야 될 때는 언제일까요? 디버깅하거나 정말로 commit 직전에, 변경 사항이 올바르게 되었는지 확인하기 위핵서 강제 호출이 되어야 한다면, 우리가 flush()가 호출되는 시점이 언제인지는 알아야겠죠?

  • Transaction commit시 호출
  • JPQL 쿼리 실행시 호출

보통 JPA를 기본 옵션으로 사용하게 되면 위와 같이 commit 할 때와 JPQL 쿼리를 실행할 때, flush 메소드를 호출하게 됩니다. JPQL이란 Java Persistence Query Language의 약자로 Java에서 사용할 수 있는 객체 지향 쿼리 언어입니다. Java에 맞춰 SQL 쿼리를 추상화 했기 때문에 모든 DB와 연동되지만 애플리케이션 레벨에만 있다가 DB에서는 다시 SQL로 바뀌게 되는 원리입니다.

// EntityManager 생성
EntityManager em = emf.createEntityManager();

// 중간에 Query 조회
query = em.createQuery("select c from Cafe c", Cafe.class);
List<Cafe> cafeMenus = query.getResultList();

Spring Data JPA에서는 @Query 어노테이션을 이용하여 JPQL을 사용할 수 있는 방법이 있습니다. 일반 JPA에서도 EntityManager 객체를 이용하여 createQuery 메소드를 이용하면 JPQL을 사용할 수 있습니다. 이 때는 DDL이든 DML이든 Query를 실행하기 전, 자동으로 flush 메소드가 호출됩니다.

왜 자동으로 발생하는 걸까요? 그것은 EntityManager의 기본 flush 옵션이 FlushModeType.AUTO로 되어 있기 때문입니다.

em.setFlushMode(FlushModeType.COMMIT);

만약, 커밋할 때만 flush 메소드를 호출하게 하고 싶다면, 위와 같이 EntityManager 객체의 flushMode 타입을 COMMIT으로 설정해주면 다음부터 JPQL을 사용할 땐 flush 메소드를 호출하지 않습니다.

JPQL을 사용했을 때, flush 메소드가 필요한 경우는 언제일까요? 바로 영속성 컨텍스트에 없는 테이블을 조회할 때는 flush 메소드가 필요합니다. 애플리케이션에 있는 도메인에 없는 테이블을 조회해야 할 경우, 영속 상태로 데이터베이스를 조회할 수 있는 방법이 없습니다. 따라서 이 때는 JPQL을 사용해야 하는데, flush 메소드를 통해서 동기화 한다면, JPA의 이점을 그대로 살릴 수 있는 장점이 있죠.

 

마치며...

여기까지 간단하게 flush 메소드에 대해서 알아봤습니다. 여태까지 commit 메소드를 호출해서 DB에 반영되었다고 생각했는데, flush 메소드를 보게 되면서 JPA가 DB와 정확하게 동기화 할 수 있는 매커니즘의 동작 방식을 알게 된 계기가 되었습니다.

TAGS. ,
comments powered by Disqus

Tistory Comments 0