ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Jpa 에서 Bulk Data Update시 정합성 문제
    Spring & JPA 2024. 1. 4. 19:00

     

     

    발단

     

    내가 맡은 도메인은 쿠폰이다. 

     

    쿠폰에는 유효기간이 있고, Spring Scheduler를 이용해서 

    매일 자정에 유효기간이 지난 쿠폰을 삭제 대신 boolean으로 저장된 값을 바꿔서 무효한 쿠폰이 되도록 해야한다.

     

    처음에 빠르게

    Service 레이어에서 Spring Data Jpa를 통해 쿠폰리스트를 싹다 가져온 뒤

    도메인 내 로직을 통해 쿠폰을 무효화 시켜 다시 저장시키는걸 작성해보았다.

     

    그런데 웬걸!!! 

     

    jpa를 통해서 가져온 데이터를 수정해서 다시 DB로 저장시키면 (물론 Dirty Checking을 통해 자동으로 저장된다)

    이게 가져온 데이터가 전부 다 단건으로 Update 쿼리가 나간다.

     

    만약 수백개의 쿠폰이 바뀌어야하면 수백개 쿼리가 나간다;;;

    이게 수만개가 되면 음...

    10만개 기준

     


    극복

     

    이를 해결할 방법은 지난번에 사용해본 Bulk 연산이었다.(이전 게시글 참조)

    쿼리문이 많이 나갈땐 Bulk 연산을 통해 단 1개의 쿼리로 바꿀 수 있다.

     

     public void updateExpiredCoupons() {
            queryFactory
                    .update(coupon)
                    .set(coupon.availableYn, false)
                    .where(coupon.availableYn.isTrue().and(coupon.validPeriodEndDate.after(LocalDateTime.now())))
                    .execute();
        
        }

     

    bulk update는 querydsl을 이용했다.

     

     

    역시나 빠르게 실행 가능했다.

    그런데 여기서 문제가 다시 생겼다.

     


     

    다시 발생한 문제

     

    영속성 컨텍스트에 기존의 엔티티가 존재하면 다시 DB에서 가져오지 않는다.

     

      public void Test1() { //예시
            List<Coupon> all = couponJpaRepository.findAll();
    
            List<Coupon> all1 = couponJpaRepository.findAll();
    
        }

     

    이 코드를 예시로 들면 첫번째 all을 가져오는 findAll()은 실행되지만,  두번째 findAll()은 실행되지 않는다. 

    all1 의 값을 확인해보면  all값이 확인된다. 

     

    실행이 되려면 영속성 컨텍스트에서 기존 엔티티값이 비워져야한다.

     


    진짜 해결

     

    다시 아까  Bulk 쿼리로 돌아가서,

     public void updateExpiredCoupons() {
            queryFactory
                    .update(coupon)
                    .set(coupon.availableYn, false)
                    .where(coupon.availableYn.isTrue().and(coupon.validPeriodEndDate.after(LocalDateTime.now())))
                    .execute();
        	em.flush() // 이 상황에서 flush는 필수가 아니다
            em.clear()
        }

     

    해당 메서드는 영속성 컨텍스트에는 변경사항없이 

    쿼리를 통해 DB에 반영이 되기 때문에

    같은 영속성 컨텍스트(@Transactional)안에서 다시 불러오면 바뀐 DB 내용을 불러오지 못한다.

     

    따라서 수동으로 (entity manager를 선언,주입 후) flush(), clear()를 해서 한번 비워져야 

    바뀐 DB값을 불러올 수 있다. 

     

     


     

    여기서는 QueryDsl 을 사용했기 때문에 entity manager를 이용한 방식이었지만

     

    @Query를 이용할땐 @Modifying(clearAutomatically = true) 옵션으로

    쿼리문 이후 영속성 컨텍스트의 1차 캐시를 비워서 데이터의 정합성을 지킬 수 있다.

     

     

     

    사실 이렇게 벌크 Update나 Delete(삭제 연산에도 이런 경우가 있다고한다) 를 한 뒤에

    하나의 @Transactional , 영속성 컨텍스트 내에서 같은 데이터를 또 찾는 경우가 실무에 많지 않다고 한다. 

     

    그럼에도 나같은 경우가 언제 또 생길지 몰라서 , 확실하게 정리하고 넘어가고자 했다!

Designed by Tistory.