JPA delete 메서드를 살펴보던 중, 생각보다 많은 종류가 있다는 걸 알게 되었다. 이 중에서 어떤 메서드를 선택해야 할지 고민이 필요하여 이를 정리해본다.

1. JPA의 delete 메서드 종류
1-1. 기본 개별 삭제 메서드
특정 엔티티 하나를 삭제하는 메서드
void delete(T entity);
1-2. 컬렉션 삭제 메서드
fun deleteAll() // 저장된 모든 데이터를 삭제
fun deleteAll(entities: Iterable<T>) // 특정 목록 삭제
fun deleteAllById(ids: Iterable<ID>) // 특정 ID 목록 삭제
- deleteAll()은 테이블에 있는 전체 데이터를 삭제하는 것이므로 매우 조심해서 사용할 것!
- 내부적으로 find 메서드를 호출하여 엔티티들을 영속성 컨텍스트에 담아놓고 개별적으로 삭제
- 개별 삭제이므로 대량 삭제시 성능이 낮을 수 있음.
1-3. 배치 삭제 메서드
fun deleteAllInBatch() // 전체 데이터 삭제
fun deleteAllInBatch(entities: Iterable<T>) // 특정 목록을 한 번의 SQL로 삭제
fun deleteAllByIdInBatch(ids: Iterable<ID>) // 특정 ID 목록을 한 번의 SQL로 삭제
- 엔티티 조회없이 모든 데이터를 한 번의 쿼리로 삭제한다.
- 엔티티 전체는 없고 id 목록만 가지고 있다면 deleteAllInBatch(entities) 대신 deleteAllByIdInBatch(ids)를 쓰면 된다.
2. 배치 삭제 메서드 사용시 주의 사항
일반 delete 메서드들은 find 메서드를 선호출 후 영속성 컨텍스트에 담아 놓고 개별 삭제 하는 반면에, 배치 삭제 메서드 메서드들(deleteAllInBatch, deleteAllInBatch, deleteAllByIdInBatch)은 조회 없이 한 번의 DELETE를 쿼리를 날리는 것을 알 수 있다.
그렇다면 무조건 성능상 배치 삭제 메서드를 쓰는 것이 좋은 것 아닌가? 라는 기쁨에 적용 후 테스트를 해보았다.
그런데 보통 실무에서는 데이터를 완전히 삭제하는 Hard Delete 보다, deleted
같은 컬럼을 활용하는 Soft Delete(논리 삭제) 를 선호하는 경우가 많다. 운영 데이터인데 쉽게 영구 삭제를 하면 되겠는가?
Hibernate에서는 @SQLDelete
어노테이션을 이용하여 JPA의 delete 메서드를 호출했을 때 실행되는 DELETE 쿼리를 커스텀해서 소프트 삭제같은 기능을 구현할 수 있다.
@Entity
@Table(name = "tb_name")
@SQLDelete(sql = "UPDATE tb_name SET is_deleted=true, date_updated=now() WHERE id=?")
class TABLE( ..
하지만 deleteAllInBatch 메서드를 통해 데이터를 삭제하니 Soft Delete가 아닌 테이블에서 완전히 Hard Delete 되어 있었다.
deleteAllInBatch과 같은 배치 삭제 메서드들은 영속성 컨텍스트를 거치지 않고 바로 SQL을 실행하기 때문에
@SQLDelete
가 적용되지 않고 데이터가 영구 삭제(Hard Delete)되는 것이었다.
**deleteAllInBatch의 문제점**
- deleteAllInBatch() → DELETE FROM table_name SQL 바로 실행
- 영속성 컨텍스트를 거치지 않음 → @SQLDelete 어노테이션 무시됨 → is_deleted=true로 업데이트 되지 않고 진짜 삭제됨!
bulk 하게 Soft Delete를 실행하려면?
JPA의 기본 배치 삭제 메서드가 아닌 직접 UPDATE 쿼리를 실행해야 한다.
1) @Modifying을 사용한 배치 소프트 삭제
interface DataRepository : JpaRepository<Review, Long> {
@Modifying
@Query("UPDATE tb_name r SET r.deleted = true WHERE r.id IN :ids")
fun softDeleteByIds(@Param("ids") ids: List<Long>)
}
2) findAllById()
+ delete()
로 대체하기
@Transactional
fun deleteAll(ids: List<Long>) {
val items = repository.findAllById(ids)
items.forEach { repository.delete(it) }
}
- findAllById 호출에 의해 영속성 컨텍스트에 담기므로
@SQLDelete
적용됨 - 하지만 대량 데이터를 삭제할 때 조회 후 개별 삭제하는 방식이라 성능은 다소 떨어질 수 있음
성능이 중요하다면 @Modifying을 이용하여 직접 작성한 JPQL 쿼리를 실행하는 방식으로,
성능에 큰 첼린지가 없다면 JPA repository를 이용한 조회 후 삭제 방식을 사용하면 될 것 같다.
'개발 > JPA' 카테고리의 다른 글
JPA에서 지연로딩(LAZY LOADING)이란 (0) | 2021.09.25 |
---|---|
JPA에서 save할때 select 쿼리가 먼저 실행되는 이유 (6) | 2021.09.23 |