[JPA] @Modifying의 flushAutomatically 옵션은 언제 쓰지?

강병철·2022년 12월 16일

공부

목록 보기
3/3

@Modifying

Spring Data JPA에서 @Query를 이용해 INSERTUPDATEDELETE 쿼리를 작성할 경우 붙여줘야하는 어노테이션이다(Select는 아님)

	@Transactional
	@Modifying(clearAutomatically = true, flushAutomatically = true)
	@Query("delete from Bookmark b where b.product.id = :productId")
	int deleteAllByProductId(@Param("productId") Long productId);

❗붙여주지 않으면 런타임에 해당 쿼리 호출 시 QueryExecutionRequestException이 발생한다

여기에 줄 수 있는 옵션에는 2가지가 있다

  • flushAutomatically : 쿼리 실행 쓰기 지연 저장소의 쿼리를 flush 하는 옵션
  • clearAutomatically : 쿼리 실행 영속성 컨텍스트를 비우는 옵션

clearAutomatically = true

@Query 쿼리 실행 후에 영속성 컨텍스트를 비워서 DB에서 수정된 값을 정상적으로 조회할 수 있게 하기 위해 사용한다

clearAutomatically는 사용하는 이유는 이해하기 어렵지 않았다.
설명을 위해 이상한 예시를 작성해보자면 :

@Transactional
	public void changeName(Long productId) {
		Product product = productRepository.findProductById(productId); // 1. 조회
		System.out.println(product.getName()); // "대나무" 출력		

		productRepository.changeNameToBamboo(); // 2. 모든 product 이름을 Bamboo로 바꾼다
		
		Product productAgain = productRepository.findProductById(productId); // 3. 다시 조회
		System.out.println(productAgain.getName());	
	}
  • productRepository.changeNameToBamboo() 에는 @Query를 이용해 쿼리를 작성하되, @ModifyingclearAutomatically = true 옵션은 주지 않은 상태라고 한다면

    1. product 조회 시 영속성 컨텍스트에 저장됨
    2. 모든 product 이름을 “Bamboo”로 바꾸는 업데이트 쿼리를 실행한다.
    3. 1의 product를 다시 조회해온다
    4. product name을 출력한다

4의 결과는???? 그대로 “대나무”가 출력된다. findProductById로 꺼내왔음에도 “Bamboo”로 바뀌지 않았다.

  • 1번에서 조회해왔던 product가 영속성 컨텍스트에 그대로 남아있어서, 3번에서 findProductById 호출을 해도 기존 영속성 컨텍스트의 값을 그대로 사용하기 때문이다

그렇기 때문에 update 쿼리 실행 후에 clearAutomatically=true 옵션을 통해 영속성 컨텍스트를 비워줘야, DB에서 수정된 값을 정상적으로 조회할 수 있다.

이러한 이유로 @Modifying을 사용 시에는 clearAutomatically=true 옵션을 항상 붙여주는 게 좋을 것 같다.


flushAutomatically = true

@Query 쿼리 실행 전에 쓰기 지연 저장소에 남아있는 쿼리를 미리 flush 하고자 할 때 사용한다

이 옵션이 왜 필요한지 이해가 되지 않았다.

왜냐하면 트랜잭션 내에서 JPQL를 실행하면, 영속성 컨텍스트가 flush 된다고 알고 있었기 때문이다.

어차피 flush 되는데 이 옵션이 왜 필요한가..?

즉 아래의 코드를 보면 (changeNameToBamboo() 에는 clearAutomatically=true만 붙어있는 상태)

@Transactional
	public void update(Long productId) {
		Product product = productRepository.findProductById(productId);
		
		product.changePrice("10000");
		System.out.println("전 @@@@@@@@@@@@@@@@@@@@@@@");
		productRepository.changeNameToBamboo(); 
		System.out.println("후 @@@@@@@@@@@@@@@@@@@@@@@");

	}
  • changeNameToBamboo() 호출 시,
    1. product.changePrice()에 의해 쓰기 지연 저장소에 저장되어있던 update 쿼리가 먼저 실행된 뒤에
    2. changeNameToBamboo()의 쿼리가 실행된다.

전@@, 후@@ 출력 문 사이에서 업데이트 쿼리가 2개 발생한다.

flushAutomatically = true 옵션 없이도 changeNameToBamboo()에 의해 자동으로 flush가 되는 것이다


그런데 더 찾아본 결과, JPQL을 실행한다고 항상 flush가 되는 것은 아니었다.

https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#flushing-auto

AUTO flush

By default, Hibernate uses the AUTO flush mode which triggers a flush in the following circumstances:

  • prior to committing a Transaction
  • prior to executing a JPQL/HQL query that overlaps with the queued entity actions
  • before executing any native SQL query that has no registered synchronization

즉, 내가 이해한 바로는 실행될 JPQL에 의해서 영향을 받는 Entity들에 대해서만 flush가 된다는 것이었다.
(물론 이건 FlushMode 옵션이 기본값인 FlushModeType.AUTO 일 때!)


그래서 또 간단히 확인을 해보니 진짜 그랬다
(이때 deleteAllByProductId()에는 역시나 clearAutomatically=true 만 붙어있다)

@Transactional
	public void update(Long productId) {
		Product product = productRepository.findProductById(productId);
		
		product.changePrice("10000");
		System.out.println("전 @@@@@@@@@@@@@@@@@@@@@@@");
		bookmarkRepository.deleteAllByProductId(product.getId());
		System.out.println("후 @@@@@@@@@@@@@@@@@@@@@@@");

	}
  • 전@@, 후@@ 사이에서는 product의 업데이트 쿼리는 실행되지 않는다

  • HibernatedeleteAllByProductId()의 쿼리가 product에는 영향을 미치지 않는다고 판단하여 product.changePrice() 쿼리를 flush를 시키지 않은 것이다.


그렇담 위의 경우 product 가격을 10000원으로 변경하는 쿼리는 메서드가 끝날 때, Dirty Checking(변경 감지)에 의해서 Transaction이 커밋되면서 flush 돼야한다.

하지만 해당 쿼리는 어디서도 발생하지 않는다. 그 쿼리를 우리가 이미 삭제해버렸기 때문이다.

바로 deleteAllByProductId() 호출을 할 때 clearAutomatically=true 옵션에 의해서 영속성 컨텍스트가 싹 비워졌다. 즉, 쓰기 지연 저장소에 저장되어 있던 product.changePrice() 쿼리도 삭제되어버린 것이다.

이런 경우를 방지하기 위해flushAutomatically = true 옵션도 필요했던 것이다.


끝!

📚 참고

김영한님의 스프링 데이터 JPA 강의 - 벌크성 수정 쿼리 편

https://vladmihalcea.com/how-does-the-auto-flush-work-in-jpa-and-hibernate/

0개의 댓글