💻 Backend

JPA 와 QueryDSL 삭제(delete) 동작의 차이

date
slug
jpa-querydsl-deleteall
author
status
Public
tags
SpringBoot
Java
summary
JPA 그리고 QueryDSL 사용 시 delete, deleteAll 의 동작 차이점
type
Post
thumbnail
category
💻 Backend
updatedAt
Jul 14, 2025 12:23 AM
 
 
 

JPA 와 QueryDSL 삭제(delete) 동작의 차이점


주요 차이점

JPA와 QueryDSL 모두 deleteAll 기능을 제공하지만, 동작 방식과 1차 캐시(영속성 컨텍스트)에 미치는 영향에서 중요한 차이가 있다.
 

1. JpaRepository.deleteAll()

  • 내부적으로 루프를 돌며 각 엔티티에 대해 em.remove() 호출
  • 즉시 DB에 반영되지는 않지만, 1차 캐시에서 해당 엔티티는 제거
    • em.remove()는 해당 엔티티를 삭제 상태로 마킹하며, 실제 DELETE SQL은 flush 또는 commit 시점에 실행
  • 트랜잭션 커밋 또는 em.flush() 시점에 실제 SQL DELETE 실행
@Transactional public void deleteMembersAndSave() { memberRepository.deleteAll(members); // em.remove 반복: 1차 캐시내에서도 즉시 제거 memberRepository.saveAll(newMembers); // 안전하게 수행 가능 }
1차 캐시와 DB 간 상태 일치 보장
 

2. QueryDSL의 delete() 또는 deleteAll

  • 내부적으로 JPQL DML (delete from Entity e where ...)을 생성
    • EntityManager.createQuery(...).executeUpdate() 로 실행됨
  • 대상 엔티티를 영속성 컨텍스트에 올리지 않고 직접 SQL로 삭제
    • 1차 캐시에 이미 존재하는 동일 엔티티와 불일치할 수 있음
queryFactory .delete(member) .where(member.age.gt(30)) .execute(); entityManager.clear(); // 반드시 캐시 초기화 필요
1차 캐시에 남은 값으로 인해 예기치 않은 동작 발생 가능성 존재
 

캐시 동기화 필요성

방식
1차 캐시 동기화 여부
추가 작업 필요 여부
JPA deleteAll()
✅ 자동 반영
❌ 없음
QueryDSL delete()
❌ 반영 안 됨
✅ em.clear() 필요
 

캐시 동기화 필요성이 왜 차이가 날까?

QueryDSL은 단순히 “빌더 API”이다.
JPA 기반일 경우 내부적으로 JPA가 이해할 수 있는 JPQL을 타입 안전하게 생성하는 빌더 역할을 함 즉, JPA 쿼리를 “직접” 사용하는 건 아니지만 JPQL을 타입 안전하게 “생성해서 JPA가 실행”하도록 위임하는 방식임
  • 내부적으로 JPQL 생성하고, 이 JPQL은 JPA의 EntityManager를 통해 실행
select m.name from Member m where m.age > 20
  • 실행 순서
      1. QueryDSL: 타입 안전한 방식으로 JPQL 문자열을 생성
        1. List<Member> result = queryFactory .selectFrom(member) .where(member.age.gt(30)) .fetch();
          QueryDSL + JPA 코드 작성
          select m from Member m where m.age > 30
          QueryDSL 이 JPQL 생성
      1. EntityManager.createQuery(…) 호출
        1. entityManager.createQuery("select m from Member m where m.age > 30", Member.class);
          JPA 처리 시작
      1. JPA 구현체(Hibernate 등):
          • JPQL 파싱
          • 적절한 SQL로 변환
          • 영속성 컨텍스트 사용 여부 결정
          SELECT m.id, m.name, m.age FROM member m WHERE m.age > 30
          Hibernate 가 JPQL을 SQL로 번역한 결과
      1. SQL 실행 (JDBC):
          • DB에 SQL 쿼리 전송
          • 결과를 JDBC ResultSet으로 수신
      1. 결과 매핑:
          • SQL 결과 → 엔티티(Member)로 매핑
          • 필요시 영속성 컨텍스트(1차 캐시)에 등록
          ⚠️ 단, bulk delete/update(JPQL DML)는 엔티티를 영속성 컨텍스트에 등록하지 않으며 1차 캐시와의 동기화가 이루어지지 않으므로 이후 작업에서는 직접 clear() 또는 flush()를 고려해야 함
 

추가로 이해하기

한번 더 질문하기

Q. QueryDSL은 내부적으로 JPA 쿼리를 쓰는가?
A. JPA의 CriteriaQuery를 쓰는 건 아님
JPA가 이해할 수 있는 JPQL 문자열타입 안전하게 생성해서 넘기고
최종적으로 실행하는 주체는 EntityManager(JPA) 는 맞음
 

요약 내용

  • JPA 에서 delete 사용 시 즉시 1차 캐시에서도 삭제 마킹(em.remove)
    • em.remove()는 객체 단위 삭제로, 1차 캐시에 있는 엔티티도 함께 제거됨
  • QueryDSL 을 사용하는 경우 내부적으로 JPQL 을 생성하기에 1차 캐시 초기화는 별도로 작성해야함
    • JPQL DML (delete/update)은 객체를 무시하고 SQL만 날림 캐시 무시
    • QueryDSL은 JPA가 이해할 수 있는 JPQL을 타입 안전하게 생성하는 빌더 역할
      • 실행 주체는 JPA의 EntityManager: JPQL을 SQL로 변환 후 JDBC로 실행
 

참고링크

 

 

Comment

JPA의 deleteAll()은 단건 삭제를 반복하기 때문에 성능상 이슈가 있을 수 있으나, 데이터 일관성과 영속성 컨텍스트의 동기화 측면에서는 매우 안전하다.
반면 QueryDSL의 bulk delete는 성능은 좋지만, 항상 flush()/clear()를 명시적으로 호출해주는 것이 안전하다.