Spring JPA JPQL를 사용하면 flush되는 현상
JPQL(Java Persistence Query Languate)를 사용할 경우 1차 캐시라고 부르는 **영속성 컨텍스트(Persistent Context)**와 관계 없이 flush가 자동으로 실행 됩니다. 이렇게 자동으로 flush되는 이유는 영속성 컨텍스트가 변경된 상태에서 JPQL이 선행되면 DB와 영속성 컨텍스트의 일관성이 깨질 수 있기 때문입니다.
public interface ItemGroupRepository extends JpaRepository<ItemGroup, Long> {
@Query("SELECT ig FROM ItemGroup ig WHERE ig.itemCategory.goodsCatSeq = :goodsCatSeq")
List<ItemGroup> findAllByItemCategoryGoodsCatSeq(@Param("goodsCatSeq") Long goodsCatSeq);
@Query("UPDATE ItemGroup ig SET ig.goodsOrderNo = :orderNo WHERE ig.goodsGrpSeq = :goodsGrpSeq")
void updateItemCategoryOrderNo(@Param("goodsGrpSeq") Long goodsGrpSeq, @Param("goodsOrderNo") Integer goodsOrderNo);
}
예를들어 JPQL 전에 dirth checking으로 실행될 예정인 메서드가 존재한다면 flush가 발생하여 먼저 실행되게 됩니다.
@Test
@DisplayName("JPQL 영속성 테스트")
@Transactional
void jpql_persistence_test() {
// given
Long goodsCatSeq = 8L;
// when
ItemCategory itemCategory = this.itemCategoryRepository.findById(goodsCatSeq)
.orElseThrow(() -> new EmptyResultDataAccessException("상품 카테고리가 존재하지 않습니다.", 1));
this.itemCategoryRepository.delete(itemCategory); // (1) 원래는 dirty checking으로 함수가 종료될 때 실행되어야 할 부분
// (2) JPQL을 호출하면서 자동 flush가 발생하고, (1)이 먼저 실행되고, 아래 JPQL이 실행됩니다.
List<ItemGroup> result = this.itemGroupRepository.findAllByItemCategoryGoodsCatSeq(goodsCatSeq);
...
}
또한 JPQL로 DML(INSERT, UPDATE, DELETE)로 DB를 변경할 경우 **영속성 컨텍스트에 반영되지 않기 때문에 논리적 오류가 발생할 수 있습니다.**
이러한 현상을 막기 위해서는 JPQL 메서드에 @Modifying를 추가하여 JPQL이 실행 후 그 결과를 영속성 컨텍스에 반영 되도록 해야 합니다.
@Modifying에는 clearAutomatically와 flushAutomatically 두 가지 속성 값이 있습니다.
- clearAutomatically : 자동으로 영속성 컨텍스트를 비우고 최신 상태를 유지하도록 할지 여부 값입니다. default 값은 false이며 ntityManager.clear()와 동일한 기능을 합니다.
-
flushAutomatically: 자동으로 영속성 컨텍스트를 flush할지 여부 값으로 default 값은 false입니다. 그러나 JPA 구현체인 Hibernate의 flushMode(AUTO COMMIT)의 default값이 AUTO로 설정되어 있어 flushAutomatically값이 무시되고 flush가 발생하는 것 같습니다.
한가지 주의할 점은 clearAutomatically를 default값인 false로 사용할 경우 DB에 쿼리 반영 후 영속성 컨텍스트에는 반영되지 않습니다.
@Test
@DisplayName("JPQL Modifying 영속성 테스트")
@Transactional
void jpql_modifying_persistent_test() {
Long goodsGrpSeq = 11L;
ItemGroup itemGroup = this.itemGroupRepository.findById(goodsGrpSeq)
.orElseThrow(() -> new EmptyResultDataAccessException("상품 카테고리가 존재하지 않습니다.", 1));
log.info(">>> Before getGoodsOrderNo: " + itemGroup.getGoodsOrderNo()); // (1) Before getGoodsOrderNo: 3
this.itemGroupRepository.updateItemCategoryOrderNo(goodsGrpSeq, 1);
ItemGroup result = this.itemGroupRepository.findById(goodsGrpSeq).orElseThrow();
log.info(">>> After getGoodsOrderNo: " + result.getGoodsOrderNo()); // (2) After getGoodsOrderNo: 3
}
아래와 같이 clearAutomatically를 true로 설정하면 정상적으로 영속성 컨텍스트로 반영되는 것을 확인할 수 있습니다.
@Modifying(clearAutomatically = true)
@Query("UPDATE ItemGroup ig SET ig.goodsOrderNo = :goodsOrderNo WHERE ig.goodsGrpSeq = :goodsGrpSeq")
void updateItemCategoryOrderNo(@Param("goodsGrpSeq") Long goodsGrpSeq, @Param("goodsOrderNo") Integer goodsOrderNo);
log.info(">>> Before getGoodsOrderNo: " + itemGroup.getGoodsOrderNo()); // (1) Before getGoodsOrderNo: 3
...
log.info(">>> After getGoodsOrderNo: " + result.getGoodsOrderNo()); // (2) After getGoodsOrderNo: 1
댓글남기기