본문 바로가기

소소한 꿀팁..?

[Data JPA] delete구문에서의 Batch

https://goto-pangyo.tistory.com/218

 

1. data jpa가 제공해주는 deleteAll에 대한 의문점 (컨트리뷰션 실패)

두번째 컨트리뷰션 도전입니다...! 아직 한발 남았다.. 남은 한발이 불발탄인줄은 몰랐습니다.. 이미 deleteAll로 하였을 경우 단건씩 나가는 문제를 해결한 메소드인 delete batch가 있다고 합니다...

goto-pangyo.tistory.com

제가 이전에 느낀 불편한 점에 대한 컨트리뷰션을 준비하면서 배운것이 있어 글로 작성하고자 합니다.

위의 링크는 컨트리뷰션을 준비하며 작성한 글입니다. 해당 게시글을 요약하자면 

data jpa를 통해 여러건을 한번에 지우고자 할때 deleteAll은 한건씩 삭제한다. 그렇기에 deleteBatch를 사용해 하나의 쿼리로 삭제가 가능하게끔 구현해야 한다.

그러나 저는 해당 게시글을 쓰며 "그러면 무조건 deleteAll보다는 deleteAllBatch가 훨씬 좋은것 아닌가? 둘의 차이는 뭐지?" 라는 생각이 들었습니다. 그렇기에 관련 정보글을 여기저기서 찾아보며 만약 유의미한 차이가 없으면 이 역시도 컨트리뷰션을 하고자 하였습니다.

 

그리고 아래는 제가 찾은 정보에 대한 요약입니다.

  1. deleteAllBy 메서드는 삭제하고자 하는 데이터를 select 구문으로 전부 가져오고 단건으로 삭제가 된다.
  2. deleteAllInBatch 메서드는 삭제하고자 하는 데이터를 하나의 쿼리로 묶어 삭제한다.
  3. 이때 둘의 차이는 아래와 같다.
    1. Batch 키워드로 삭제할 경우 JPA와 하이버네이트의 라이프 사이클을 유지하지 못한다. 즉 @PreDelete 와 같은 어노테이션이 적용되지 않는다.
    2. 다른 엔티티에 cascade(전파)되지 않는다.
    3. 지우고자 하는 데이터가 JPA의 영속성 컨텍스트에 존재할 경우 자동으로 삭제되지 않습니다.

쉽게 말해 delete구문을 Batch 연산을 통해 수행 할 경우 JPA의 영속성 컨텍스트나 캐시를 거치지 않고 바로 DB로 쿼리를 보냅니다.

그렇기 때문에 함부로 Batch를 사용하면 영속성 컨텍스트에는 있다고 나와 있지만 실제 DB에는 없어 Exception이 발생하는 상황이 생길수도 있을것 같다는 생각이 듭니다.

 

아래는 제가 직접 확인한 코드와 결과창입니다.

 

테스트용으로 DB에 insert하고 delete할 엔티티입니다. 여기서 주목하셔야 할 점은 @PostRemove 입니다. 해당 어노테이션은 flush 혹은 commit을 수행할 경우 자동으로 호출해주는 일종의 이벤트를 처리해주는 어노테이션입니다.

@Entity
@NoArgsConstructor
@Data
public class Member {

    @Id
    @GeneratedValue(strategy = AUTO)
    private Long id;

    private String clubName;

    @Builder
    public Member(Long id, String clubName) {
        this.id = id;
        this.clubName = clubName;
    }

    @PostRemove
    public void afterDestroy(){
        System.out.println("success!!");
    }
}

 

멤버 엔티티에 대한 JPA인터페이스입니다.

public interface MemberRepository extends JpaRepository<Member, Long> {

}

 

아래 코드는 Junut을 이용해 테스트하는 코드 입니다. 저는 @BeforEach를 통해 테스트용 더미데이터를 집어넣고 있으며 만약 영속성 컨텍스트를 통해 커밋 혹은 플러시가 된다면 Member 클래스에 작성한 @PostRemove 어노테이션을 통해 success라는 문구를 출력할 것입니다.

@SpringBootTest
class MemberRepositoryTest {

    @Autowired
    private MemberRepository memberRepository;

    @BeforeEach
    public void setUp() {
        for (int i = 0; i < 10; i++) {
            memberRepository.save(Member.builder()
                    .clubName("test" + i)
                    .build());
        }
    }


    @Test
    @DisplayName("deleteAll을 이용해 삭제하며 PostRemove가 호출된다.")
    public void deleteAllTest() {
        memberRepository.deleteAll();
    }

    @Test
    @DisplayName("deleteAllInBatch를 이용해 삭제하며 PostRemove가 호출되지 않는다.")
    public void deleteAllInBatchTest() {
        memberRepository.deleteAllInBatch();
    }

    @Test
    public void deleteByIdTest() {
        memberRepository.deleteAllByIdInBatch(List.of(1L, 2L, 3L));
    }
}

Data Jpa에서 제공해주는 Batch키워드를 비추어 다시 설명하자면 Batch키워드가 없는 메소드들은 값을 영속성 컨텍스트를 통해 정확히는 EntityManager를 통해 commit되고 flush가 될것입니다. 그리고 @PostRemove를 통해 삭제되는 이벤트를 확인할 수 있을 것입니다.

 

반대로 Batch 키워드가 있는 경우 EntityManager를 통해 commit되고 flush가 되지 않기 때문에 실제로 삭제된다 하더라도 @PostRemove를 통해 삭제되는 이벤트를 확인할수 없을 것입니다.

 

결과

위는 deleteAllTest 메소드를 수행했을때의 결과입니다.

 

그리고 아래는 deleteAllInBatchTest 메소드를 수행했을때의 결과입니다.

+

deleteInBatch를 통해 삭제하였을때에도 @PostRemove와 같은 이벤트 리스너가 동작하도록 컨트리뷰션을 시도해보고자 합니다.

(3번째 도전 후후..)

ref

https://stackoverflow.com/questions/26142261/spring-jparepostory-delete-vs-deleteinbatch

https://ttl-blog.tistory.com/191