본문 바로가기

컨트리뷰션/DataJPA

4. batch메서드 컨트리뷰션 (실패)

목차

개요

사전지식

문제점

제안

레퍼런스

결과


📣 개요

작년 6월 즈음, 우연히 학우가 컨트리뷰션에 도전을 하였고 성공하였다는 발표를 듣게 되었습니다. 당시 저는 '그렇구나...' 정도의 감상이 전부였으나 올해 초 학교생활도 마무리되는 시점에서 이때 들었는 컨트리뷰션을 한 번쯤 해보고 싶다는 생각을 하게 되었습니다. 당시 저는 쿼리 최적화에 관심이 있었기에 제가 자주 사용하는 data jpa의 컨트리뷰션에 도전하게 되었습니다.

 

이미 여러번 시도를 한 흔적...


📣 사전 지식

개요에서 이미 언급하였다시피 저는 ORM의 쿼리 최적화에 관심이 많았습니다. 제가 자주 사용하는 Data JPA의 deleteAll은 엔티티를 하나씩 삭제를 하는 문제가 존재했습니다.

 

JpaRepository를 상속받은 memberRepository에서 deleteAll을 하였을때 DB에 나가는 쿼리들입니다.

 

그리고 이러한 문제를 data jpa에서는 'batch'키워드가 들어간 메서드로 지원해주고 있습니다.

 

JpaRepository에서 'Batch'키워드가 들어간 메소드를 지원해주고 있습니다.

 

그리고 이를 통해 삭제를 하게 되면 아래의 사진과 같이 하나의 쿼리로 삭제가 됩니다.

 

다음과 같이 한번에 삭제가 됩니다.

➕추가

더보기
인자가 있어도 'IN'키워드로 한번에 삭제가 가능합니다.

 

그러나 'batch' 키워드를 사용할 경우 영속성 콘텍스트를 거쳐 삭제를 하는 것이 아닌 DB에 바로 삭제 쿼리를 보냅니다.

이는 영속성 콘텍스트에서 엔티티에 변화가 있을 경우 호출되는 엔티티 리스너 중 하나인 @PostRemove를 통해 확인 가능합니다.

 

아래의 더보기에서 @PostRemove가 있을 때 deleteAll() 메서드를 호출하였을 때와 deleteAllInaBatch() 메서드를 호출하였을 때 콘솔에 찍히는 로그의 차이를 확인할 수 있습니다.

더보기
deleteAll()를 통해 엔티티가 삭제될때마다 출력되는 로그를 확인할 수 있습니다.

 

deleteAllInBatch()를 통해 삭제될때는 로그가 찍히지 않는 것을 확인할 수 있습니다.

 


📣 문제점

이런 경우 다음과 같은 상황에서 다음과 같은 문제점이 발생할 수 있다 생각하였습니다.

 

JPA 엔티티 리스너에 로직을 의존하고 있을 경우 의도한 동작이 수행되지 않을 수 있다.

 


📣 제안

'Batch' 메서드가 있는 SimpleJpaRepository<T, ID> 클래스를 컨트리뷰트 하고자 합니다.

Entity Manager에서 특정 엔티티를 삭제하는 것은 Entity Manager의 detach(), remove(), clear()메소드를 통해 구현하고자 합니다.

 

deleteAllInBatch(Iterable<T> entities)

function deleteAllInBatch(Iterable<T> entities) {
    'entities' 매개변수가 null이 아닌지 확인한다.
    if ('entities'에 요소가 있다) {
    
        // 컨트리뷰트 시작
        if(entityManager에 유형 매개변수 T가 존재한다면)
            EntityManager에 있는 모든 유형 매개변수 T의 인스턴스를 remove합니다.        
           
        entityManager의 clear 메소드를 호출합니다.
        // 컨트리뷰트 끝
    
        엔티티 이름을 사용하여 삭제 Query 문자열 생성
        QueryUtils를 사용하여 Query 및 매개변수 적용

        삭제 Query 실행
    }
}

 

 

deleteAllByIdInBatch(Iterable<ID> ids)

function deleteAllByIdInBatch(Iterable<ID> ids) {
    'ids'가 null이 아닌지 확인
    if ('ids'가 값이 있다) {
        if (엔티티가 composite ID를 가지고 있다.) {
            composite ID를 가지고 있는 엔티티는 DB로부터 값을 가져온뒤 Batch로 한번에 삭제.
            // 만약 deleteAllInBatch 메서드가 컨트리뷰션이 된다면 수정하지 않아도 됩니다.
        } else {
        
            // 컨트리뷰션 시작
            for(ids를 전체를 순회한다.)
	            entity manger에 id를 식별자로 지니는 T타입의 인스턴스를 찾는다.
                if(해당 엔티티가 null이 아니라면)
                	entity manager에서 해당 엔티티를 remove한다.
                    
            entityManager의 clear 메소드를 호출한다.
            // 컨트리뷰션 끝
        
            삭제 Query 문자열 생성한다.
            삭제 Query 객체를 생성한다.
            'ids' 파라미터를 바인딩한다.
            Query 힌트를 적용하고 삭제 Query를 실행한다.
            
            삭제 Query 실행
        }
    }
}

📣 고민거리

deleteAllInBatch(Iterable<ID> ids), deleteAllInBatch(Iterable<T> entities)의 컨트리뷰션은 아래와 같이 요약할 수 있습니다.

인자로 넘어온 ID를 가지는 엔티티를 entityManager로부터 삭제 상태로 만들고 (필요하다면 준영속상태로 만들고) entityManager를 clear 합니다.

 

 

위와 같이 코드를 작성하여 PR를 보냈지만 여기에는 어색한 흐름이 존재합니다.

  1. entityManager의 remove 메소드를 호출하여 엔티티 리스너가 동작하게 만듭니다.
  2. remove가 끝난 이후 entityManager의 clear 메소드를 호출해 entityManager내부에 존재하는 정보를 삭제합니다.

위와 같이 코드를 작성할 경우 배치로 삭제하고자 하는 엔티티 이외에 다른 엔티티도 entiyManager에서 삭제됩니다.

 

이에 대한 해결책으로 remove 이후 detach 메소드를 호출할 경우 이론적으로 entityManager에서 원하는 메소드만을 삭제할 수 있지만 

JPA의 구현체인 하이버네이트내부 버그로 예외가 발생합니다.

또한 아래와 같이 생각하였기에 clear를 호출하는 방식으로 구현하였습니다.

  1. 배치성 작업을 띄는 메소드이므로 기타 비즈니스 로직이 필요한 상황이 발생할 확률은 낮다.
  2. 삭제쿼리 이후 영속성 컨텍스트에 존재하는 엔티티에 의존하는 로직이 추가될 가능성은 낮다.

📣 결과

 

다음과 같은 답장을 통해 컨트리뷰션에 실패하였습니다.

위 글을 요약하면 다음과 같습니다.

  1. Batch 메소드는 삭제 쿼리가 메모리를 거치지 않고 바로 DB로 날아가도록 의도했다. 그러므로 영속성 컨텍스트에서 (엔티티)리스너에게 알리지 않고 바로 삭제하는 것 역시 의도된 설계이다.
  2. EntityManager.clear()를 호출하는 것은 EntityManager에 있는 영속성 컨텍스트 내부에 존재하는 다른 엔티티까지 삭제해버릴수 있기에 호출해서는 안된다.

📣 배운 점

비록 컨트리뷰션에는 실패하였지만 컨트리뷰션을 준비하는 과정을 통해 아래의 것들을 배울수 있었습니다.

  • JPA의 엔티티의 라이프 사이클간의 관계를 보다 깊게 이해할 수 있었습니다.
  • 편하게 사용하였던 JPA의 기능들을 구현체 내부에서 살펴볼 수 있었습니다.
  • 코드를 보며 이해하는 과정을 통해 코드를 작성하는 좋은 습관은 무엇인가에 대해 고민할 수 있었습니다.

📣 레퍼런스

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

https://astrid-dm.tistory.com/486

https://jojoldu.tistory.com/415

자바 orm 표준 jpa 프로그래밍

 


📣 깃헙

https://github.com/spring-projects/spring-data-jpa/issues/3360

https://github.com/spring-projects/spring-data-jpa/pull/3591