본문 바로가기

JPA

Spring Data JPA @Modifying (2) - flushAutomatically

https://devhyogeon.tistory.com/4

 

Spring Data JPA @Modifying (1) - clearAutomatically

이 글을 작성하게 된 계기는 Spring Data JPA의 @Modifying에 있는 flushAutomatically에 대해 의문점이 생겼고, 그에 대한 학습 테스트를 해보면서 였습니다. 하지만 @Modifying의 Attribute가 clearAutomaticall..

devhyogeon.tistory.com

위 글에 이어서 Spring Data JPA가 제공하는 @Modifying Annotation의 두번째 Attribute에 대해서 살펴보겠습니다. Modifying, 벌크 연산, clearAutomatically 관련 내용은 위 글을 통해 확인하시면 됩니다.

 

위 글에서 언급했듯이 flushAutomatically에 대한 학습 테스트를 진행하던 도중 저의 예상과는 다른 결과가 나와서 의문점이 생겼고, 왜 그런 결과가 나왔는지에 대해서 궁금증을 해소하는 과정을 담아보았습니다.

 

 

 

flushAutomatically

이 Attribute는 @Query@Modifying을 통한 쿼리 메서드를 사용할 때, 해당 쿼리를 실행하기 전, 영속성 컨텍스트의 변경 사항을 DB에 flush 할 것인지를 결정하는 Attribute 입니다. Default 값은 clearAutomatically와 마찬가지로 false 입니다.

 

구체적인 설명 전, 저의 의문이 발생하게 된 학습 테스트를 살펴봅시다.

 

 

 

학습 테스트 (1) - 예상 외의 결과

@Modifying Attribute는 Default 값이 false입니다. 즉, 해당 쿼리 메서드를 실행하고 쿼리를 날리 전에 flush를 자동으로 하지 않는다는 의미입니다. 그래서 저는 false일 경우, 영속성 컨텍스트와 DB의 싱크가 맞지 않을 수 있는 경우에 대해서 눈으로 확인하고 싶어 테스트를 작성해 보았습니다.

 

Article Entity

Article 엔티티입니다. isPublished 값은 Default로 false로 주었습니다. publish() 메서드를 사용해서 isPublished 값을 true로 변경할 수 있게 구현하였습니다.

 

 

ArticleRepository

ArticleRepository 입니다. 테스트를 위한 쿼리 메서드가 있습니다. @Query@Modifying을 통해 작성하였습니다. 물론 flushAutomatically 값은 Default로 false 입니다. isPublished가 TRUE인 Article들을 모두 삭제하는 쿼리 메서드입니다. return 값은 삭제한 데이터 수입니다.

 

 

Spring Data JPA를 통한 테스트

이 테스트가 바로 저를 혼란에 빠지게 한 테스트입니다. 시나리오는 다음과 같습니다.

1. Article 엔티티 생성 (Transient 상태)
2. Article 엔티티 영속화 (Persistence 상태 - DB에는 저장되지 않음)
3. ArticleRepository,findById()를 통해 DB가 아닌 영속성 컨텍스트에 있는 Article을 다시 가져옵니다.
4. 해당 Article을 publish() 메서드를 통해 isPublished 값을 TRUE로 변경합니다.
5. ArticleRepository.deletePublic()을 통해 isPublished 값이 TRUE인 Article들을 모두 삭제하는 쿼리를 날립니다.

추측 : flushAutomatically가 false이기 때문에 DELETE 쿼리를 실행하기 전 flush가 되지 않습니다. isPublished가 변경 되는              UPDATE 쿼리는 커녕 저장하는 INSERT 쿼리도 실행되지 않았을 것입니다.

6. 추측에 의하면 ArticleRepository.deletePublic()의 return 값은 0일 것이다.

결과는 어떻게 되었을까요??  테스트가 성공했을 까요??  

 

 

예상 외의 실행 결과 1
예상 외의 실행 결과 2

실행 결과는 다음과 같았습니다. 모든 것이 저의 추측과 달랐습니다. DELETE 쿼리 실행 전, flush가 되지 않기 때문에 실행되지 않을 것 같던 INSERT, UPDATE 쿼리가 실행되었고, 그 데이터를 DELETE 했기 때문에 쿼리 메서드의 return 값도 1이 나왔습니다.

 

 

예상 외의 DB 결과

DB를 확인해보아도 역시나 DELETE가 되어 비어져 있었습니다.

 

 

결과 저의 예상과 너무 달라서 혼란스러웠고, 아무리 생각해도 무엇이 문제인지를 찾지 못했습니다. 그래서 레퍼런스를 다시 보았습니다. 하지만 분명 저의 추측이 틀리지 않았습니다.

https://docs.spring.io/spring-data/data-jpa/docs/current/api/org/springframework/data/jpa/repository/Modifying.html

 

Modifying (Spring Data JPA 2.2.6.RELEASE API)

Indicates a query method should be considered as modifying query as that changes the way it needs to be executed. This annotation is only considered if used on query methods defined through a Query annotation). It's not applied on custom implementation met

docs.spring.io

다시 보아도... 분명히 해당 쿼리 메서드를 실행하기 전 flush를 할 것인지를 정하는 옵션이고, Default 값은 false인데, 왜 flush가 되는 거지???????

이런 궁금한 상태로 넘어갈 수 없어서 고민을 해본 결과 원인을 찾을 수 있었습니다.

그 원인은 바로 Spring Data JPA의 구현체인 Hibernate에 있었습니다.

 

 

 

 

Hibernate의 FlushModeType

https://docs.jboss.org/hibernate/jpa/2.1/api/javax/persistence/FlushModeType.html

 

FlushModeType (hibernate-jpa-2.1-api 1.0.0.Final API)

Flush mode setting. When queries are executed within a transaction, if FlushModeType.AUTO is set on the Query or TypedQuery object, or if the flush mode setting for the persistence context is AUTO (the default) and a flush mode setting has not been specifi

docs.jboss.org

Spring Data JPA는 구현체로 Hibernate를 사용하고 있습니다. 그래서 저희는 Hibernate를 직접 사용할 필요 없이 좀 더 쉽고, 직관적인 추상체인 Spring Data JPA를 통해 Hibernate를 사용합니다. 그리고 그 Hibernate에는 FlushModeType enum class 가 있습니다. 

그 값으로는 AUTOCOMMIT이 있습니다. Default 값은 AUTO입니다.

AUTO(Default) : flush가 쿼리 실행 시 발생한다.
COMMIT : flush가 트랜잭션이 commit 될 때 발생한다.

원인은 여기에 있었습니다. Hibernate의 FlushModeType이 Default로 AUTO였기 때문에 flushAutomatically가 false여도 쿼리 실행 전 flush가 나가게 되었고, 그래서 저의 추측이 틀리게 되었습니다.

 

 

 

 

학습 테스트 (2) - Hibernate로 직접 테스트

원인을 찾았기 때문에, 저의 추측이 맞게 테스트를 하기 위해서 먼저 Hibernate에 직접 접근해 보았습니다.

기존 Article과 ArticleRepository는 동일합니다.

 

EntityManagerJPA에서 컨텍스트 매니저를 관리하는 interface입니다. 그 내부 구현체로 사용하는 것이 Hibernate입니다.

SessionHibernate의 핵심이 되는 API입니다. EntityManager를 상속받고 있습니다.

entityManager.unwrap(Session.class) 통해 Session을 직접 사용할 수 있습니다.

사실상 저희는 JPA도, Hibernate도 직접 사용할 필요가 거의 없고, 대부분 Spring Data JPA를 사용합니다.

 

Hibernate 테스트 - FlushModeType.AUTO

먼저 Default 값인 FlushModeType.AUTO를 그대로 사용해 보았습니다. 즉, 위에서 제가 Spring Data JPA로 작성했던 테스트와 완전히 동일한 테스트입니다. FlushModeType을 찍는 것 이외에는 모든 결과가 동일하게 나오기 때문에 생략하겠습니다.

 

 

Hibernate 테스트 - FlushModeType.COMMIT

다음은 저의 처음의 추측에 맞게 수정한 테스트입니다. FlushModeType만 COMMIT으로 변경해 주었습니다.

 

 

Hibernate 테스트 - FlushModeType.COMMIT 실행 결과

드디어 테스트가 성공했습니다! 쿼리도 저의 처음 추측에 맞게 잘 실행되었습니다. DELETE 쿼리가 실행되기 전 flush가 되지 않기 때문에 INSERT, UPDATE 쿼리는 실행되지 않았고, 바로 DELETE 쿼리가 실행됩니다. 그러므로 해당 쿼리메서드의 반환값이 0이 됩니다.

그리고 최종적으로 테스트 메서드가 끝나고 commit이 되는 시점에 flush가 되게 됩니다. 즉, 이 때 INSERT, UPDATE 쿼리가 실행됩니다.

 

 

Hibernate 테스트 - FlushModeType.COMMIT DB 결과

DB에도 값이 잘 들어가 있는 것을 확인할 수 있습니다.

 

 

 

 

학습 테스트 (3) - 다시 Spring Data JPA로 테스트

그렇다면 이제 마지막으로 다시 Spring Data JPA로 저의 처음 테스트가 통과하게 만들어 보겠습니다.

간단합니다.

application.properties 또는 yml에 다음과 같은 설정만 추가해 주면됩니다.

Spring Data JPA flushMode 변경

그리고 맨 처음 Spring Data JPA 테스트를 실행해보면 통과하는 것을 확인할 수 있습니다.

(Hibernate - COMMIT  성공 테스트 결과와 동일)

 

 

그리고 여기서 다시 ArticleRepository의 해당 쿼리 메서드에 @Modifying(flushAutomatically = true)을 하게 되면 해당 쿼리 메서드를 사용할 때는 위의 설정 값이 무시되면서 다시 맨 처음 테스트와 같게 되어 테스트가 실패합니다. 즉, 쿼리 메서드 실행 전 flush가 되게 됩니다. (맨 처음 실패 테스트 결과와 동일)

 

 

 

 

결론

Spring Data JPA는 구현체로 Hibernate를 사용합니다. Hibernate의 FlushModeType의 Default 값은 AUTO입니다.

그래서 @Modifying(flushAutomatically = false)여도 해당 쿼리 메서드 실행 전 flush가 실행됩니다.

즉, flushAutomatically는 true이던 false이던 아무런 의미가 없습니다.

 

flushAutomatically가 의미를 가지게 하려면 Hibernate의 FlushModeType의 값을 COMMIT으로 변경하여야 합니다.

이 경우 flushAutomatically의 Default 값인 false를 그대로 사용하게 되면, 쿼리 메서드 실행 시 flush가 실행이 되지 않습니다. 즉, 필요한 경우에 flush를 강제 호출하거나, @Modifying 같은 경우에는 flushAutomatically를 true로 변경해야 합니다.

상당히 번거롭다는 생각이듭니다.

 

그래서 FlushModeType을 COMMIT으로 사용할 경우는 많지 않을 것 같다고 생각합니다. 그렇기 때문에 Hibernate도 Default 값을 AUTO로 하지 않았나 생각이 듭니다. 필요한 경우에만 설정 값을 변경해서 쓰면 될 것 같습니다.

 

 

 

 

 

 

예제 코드

https://github.com/men7627/devhyogeoncodes/tree/master/flushautomatically