본문 바로가기
ORM/Spring Data JPA

기본 CRUD - DELETE

by 코딩하는 랄로 2024. 1. 16.
728x90

이전 글에서는 JpaRepository의 기본 CRUD 메소드에서 READ에 해당하는 메소드들을 알아보았다. 이번 글에서는 DELETE에 해당하는 JpaRepository의 기본 메소드에 대해서 알아보자. ( 사전 작업은 다음 링크를 참고 : 기본 CRUD - READ )

 

 

DELETE

CRUD의 DELETE는 데이터베이스에서 데이터를 삭제하는 부분을 담당한다. JpaRepository의 delete를 담당하는 기본 메소드를 살펴보고 각각의 메소드가 어떤 쿼리문을 통해 작업을 진행하는지 살펴보자.

 

delete(Entity)

delete() 메소드는 넘겨받은 Entity 객체를 데이터베이스에서 삭제한다. 이 때, 주의할 점은 JPA가 어떤 객체를 Entity 객체, 즉 영속화된 객체라고 인식하는지를  알아야 한다.

 

JPA는 @Id로 지정해준 칼럼의 값이 null일 때는 일반 자바 객체로 인식하고, null이 아닐 때는 영속화된 객체, Entity Manager에 의해 관리되고 있는 Entity 객체로서 인식을 한다.

 

그렇기 때문에, delete를 하기 위한 Entity를 찾기 위해 JpaRepository의 findById 메소드를 사용해도 되지만 ( 일반적으로는 이 방식을 사용함), 아래와 같이 객체를 하나 생성하여 Id 값만 setter로 설정해주어도 된다.

@Test
void test() {
    Student student = new Student();
    student.setId(1L);
    studentRepository.delete(student);
    studentRepository.findAll().forEach(System.out::println);
}

 

이 때, 삭제하려는 데이터의 Id 값을 제외하고는 몰라도 된다!! 그 이유는 위의 코드를 동작시켜서 JPA가 실행하는 쿼리문을 통해 알 수 있다.

Hibernate:
  select
    s1_0.id,
    s1_0.address,
    s1_0.name
  from
    Student s1_0
  where
    s1_0.id=?
Hibernate:
  delete
  from
    Student
  where
    id=?
Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 <= findAll() query
Student(id=2, name=lee, address=address2)
Student(id=3, name=park, address=address3)

 

흥미롭게도, 매개변수로 받은 Entity 객체의 id 값을 이용하여 먼저 select query를 실행하여 데이터를 가져온 다음, 그 데이터의 id 컬럼을 이용하여 삭제하는 것을 알 수 있다.

 

deleteAll()

deleteAll()은 모든 데이터를 삭제하는 메소드이다.

@Test
void test() {
    studentRepository.deleteAll();
    System.out.println(studentRepository.count());
}

 

JPA는 deleteAll을 수행하기 위해서 어떤 쿼리문을 동작시키는지를 한번 살펴보자.

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0  => select
Hibernate: delete from Student where id=?  => delete
Hibernate: delete from Student where id=?  => delete
Hibernate: delete from Student where id=?  => delete

Hibernate: select count(*) from Student s1_0  => count()
0

 

delete 메소드와 마찬가지로 먼저 select 문을 통해 테이블의 모든 데이터를 가져오고, 각각의 데이터의 id 값을 통해 하나 하나 삭제해주는 동작을 한다. 삭제 전 테이블의 데이터가 총 3개가 있었기 때문에 delete문도 총 3번 실행된 것을 알 수 있다.

 

만약, 데이터의 양이 많다면 어떻게 될까? 그 횟수만큼 delete 문을 발생시키기 때문에 상당히 비효율적인 작업이고 성능 저하로 이어질 수 있기 때문에 성능면에서 JPA가 기본적으로 제공해주는 deleteAll 메소드의 사용은 지양하는 편이 좋다. 

 

deleteAll(Iterable<T>)

deleteAll 메소드의 JpaRepository를 상속받을 때 지정해준 T(Entity) 타입의 iterable한 객체를 넘겨주면 해당 데이터들을 삭제한다.

@Test
void test() {
    List<Student> studentList = studentRepository.findAllById(List.of(1L, 3L));
    studentRepository.deleteAll(studentList);
    studentRepository.findAll().forEach(System.out::println);
}

 

넘겨받은 Entity 의 id 값을 이용하여 select 문으로 데이터를 가져와서 각각에 대해서 delete 문을 호출하여 삭제한다.

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 where s1_0.id in (?,?) => findAllById

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 where s1_0.id=? => select
Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 where s1_0.id=? => select
Hibernate: delete from Student where id=? => delete
Hibernate: delete from Student where id=? => delete

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 => findAll()
Student(id=2, name=lee, address=address2)

 

deleteById(ID id)

Id 값으로 delete를 할 수 있는 메소드이다.

@Test
void test() {
    studentRepository.deleteById(1L);
    System.out.println(studentRepository.count());
}

 

코드의 동작은 위의 delete 메소드들과 같이 select 문을 먼저 실행시킨 뒤, 데이터를 가져온 후에 delete를 한다.

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 where s1_0.id=?
Hibernate: delete from Student where id=?

Hibernate: select count(*) from Student s1_0
2

 

deleteAllById(Iterable<ID>)

여러개의 id 값을 갖는 Iterable 객체를 넘겨주어 해당 id 값을 가지는 데이터를 모두 삭제하는 메소드이다.

@Test
void test() {
    studentRepository.deleteAllById(List.of(1L, 3L));
    studentRepository.findAll().forEach(System.out::println);
}

 

동작방식은 위에서 살펴본 deleteAll(Iterable<T>) 메소드처럼 똑같은 쿼리문을 통해서 동작한다.

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 where s1_0.id=?
Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 where s1_0.id=?

Hibernate: delete from Student where id=?
Hibernate: delete from Student where id=?

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0
Student(id=2, name=lee, address=address2)

 

 

 

대용량 데이터 DELETE

지금까지 delete 알아본 메소드들은 모두 select 문을 통해서 하나 하나 데이터를 확인하고 불러온 뒤에 또 하나 하나 삭제하는 과정을 거쳤다. 그렇기 때문에, 대용량의 데이터를 삭제해야 하는 경우에 매우 비효율적인 작업이 될 수 있고 그런 경우에는 해당 메소드의 사용을 지양해야 한다고 하였다.

 

이렇게 대용량의 데이터를 삭제해야 하는 경우에 사용할 수 있는 기본 메소드가 있을까? 다행히도 존재한다!! Spring Data JPA는 대용량의 데이터를 효율적으로 삭제하기 위해 deleteAllXXX 메소드 뒤의 InBatch를 붙인 메소드를 제공한다. 해당 메소드를 사용하면 JPA가 동작시키는 쿼리문이 어떻게 변화는지 보자.

 

deleteAllInBatch()

deleteAll 메소드 예제에서 메소드만 바꾼 채로 동작시켜보자.

@Test
void test() {
    studentRepository.deleteAllInBatch();
    System.out.println(studentRepository.count());
}

 

코드를 동작 시키면, 아래와 같이 select 문으로 데이터를 고르는 과정도 건너뛰고 바로 delete 호출하는 것을 볼 수 있다. 또한 각각의 데이터에 대해서 delete를 해주는 것이 아닌 한번만 호출하여 한꺼번에 delete를 해준다.

Hibernate:
  delete
  from
    Student
Hibernate:
  select
    count(*)
  from
    Student s1_0
0

 

deleteAllInBatch(Iterable<T>)

Iterable을 매개변수로 받는 deleteAll 메소드에도 InBatch를 붙인 메소드를 제공한다.

@Test
void test() {
    List<Student> studentList = studentRepository.findAllById(List.of(1L, 3L));
    studentRepository.deleteAll(studentList);
    studentRepository.findAll().forEach(System.out::println);
}

 

코드를 동작시켜보면 select 절을 생략하고 where 절에 or 를 통해, delete 문을 한번만 실행하는 것을 볼 수 있다.

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 where s1_0.id in (?,?) => findAllById

Hibernate:
  delete
  from
    Student
  where
    id=? or id=?

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0 => findAll
Student(id=2, name=lee, address=address2)

 

deleteAllByIdInBatch(Iterable<ID>)

deleteAllById 메소드도 InBatch 를 붙이 메소드로 변경하면 효율적으로 데이터를 삭제할 수 있다.

@Test
void test() {
    studentRepository.deleteAllByIdInBatch(List.of(1L, 3L));
    studentRepository.findAll().forEach(System.out::println);
}

 

코드를 동작시키면, select문을 생략하고 where 절에서 in 을 사용하여 delete 문 한번으로 삭제를 진행하는 것을 알 수 있다.

Hibernate:
  delete
  from
    Student
  where
    id in (?,?)

Hibernate: select s1_0.id, s1_0.address, s1_0.name from Student s1_0
Student(id=2, name=lee, address=address2)

 

 

 

데이터가 삭제 되었을까?

JpaRepository를 통해 제공되는 delete 메소드들은 모두 void 리턴 타입으로 리턴값이 존재하지 않는다. 그렇다면 원하는 데이터가 삭제되었는지를 어떻게 알아낼 수 있을까?

 

지금까지 예제들은 count 메소드, findAll 메소드를 통해서 데이터베이스를 확인해서 직접 알아보았지만, 코드 상으로 알아내기는 비효율적인 작업이 될 것이다. 예를 들어 삭제 전, 후의 데이터베이스 목록의 갯수를 통해 삭제 여부를 알아낸다고 하였을 때, 단순히 삭제를 하는 작업을 위해서 다음과 같은 작업이 필요하게 된다.

  • 삭제하려는 데이터가 존재하는가 => findXXX 메소드
  • 삭제 전 목록 갯수 => count() 메소드
  • 삭제 => delete 메소드
  • 삭제 후 목록 갯수 => count() 메소드

메소드만 놓고 봐도 많지만, 해당 메소드를 동작시키기 위해 실행되는 쿼리문을 생각하면 단순히 delete 작업을 위해 너무 많은 작업량이 발생하게 되는 것이다.

 

그렇다면 이를 보완할 수 있는 방안이 뭐가 있을까? 먼저 다음의 전제를 믿어야 한다.

delete 메소드는 실행되면 해당 데이터가 있을 경우, 무조건 삭제된다. 

 

delete 메소드는 무조건 삭제를 하기 때문에, 삭제가 안 되는 경우는 해당 데이터가 없는 경우 밖에 없게 되는 것이다!! ( 잘못된 삭제를 할 경우가 없기 때문에!! 그렇게 믿어야 한다!! ) 그렇기 때문에, 데이터가 존재하는지의 여부를 검사하는 단계에서 간단한 로직만 추가해주면 다른 JpaRepository의 메소드의 도움 없이도 데이터가 잘 삭제되었는지를 알 수 있다.

 

다음은 이를 적용한 예제 코드이다.

// 방법 1.
// existsById 메소드 사용
@Test
void test() {
    if(studentRepository.existsById(1L)) {
        studentRepository.deleteById(1L);
        System.out.println("Delete 성공!!");
    } else {
        System.out.println("삭제하려는 데이터가 없습니다!!");
    }
}

// 방법 2.
// findById => Optional.orElseThrow 사용
@Test
void test() {
    try {
        studentRepository.findById(4L).orElseThrow(RuntimeException::new);
        studentRepository.deleteById(1L);
        System.out.println("Delete 성공!!");
    } catch (RuntimeException e) {
        System.out.println("삭제하려는 데이터가 없습니다!!");
    }
}

 

 

728x90

'ORM > Spring Data JPA' 카테고리의 다른 글

JPA Auditing  (1) 2024.01.17
기본 CRUD - Create & Update  (0) 2024.01.16
기본 CRUD - READ  (0) 2024.01.16
JPA Repository - 개념  (0) 2024.01.15
Entity 개념 & 기본 사용법  (1) 2024.01.11