Dev/JPA

JPA OrphanRemoval(+@Where)

OK-가자 2022. 3. 2. 17:30

JPA에서 OrphanRemoval 활용하기

  • 이전에 포스트 했던 JPA Cascade에서 Remove는 없었다. Remove에 대한 설명이 많기 때문이다. 우선 Remove Cascade를 사용해보자.

CascadeType.REMOVE

        Book book2 = bookRepository.findById(1L).get();
        bookRepository.delete(book2);
//        bookRepository.deleteById(1L);
//        publisherRepository.delete((book2.getPublisher()));

        System.out.println("books : " + bookRepository.findAll());
        System.out.println("publishers : " + publisherRepository.findAll());
  • 이전에 만들 Test 코드에 Book을 삭제하는 코드를 작성하고 Test하면 Book은 지워지지만 그에 대한 publisher는 지워지지않는다. 그래서 Book 엔티티에 CascadeType.REMOVE 추가해주자.

      @ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE} )
      @ToString.Exclude
      private Publisher publisher;
  • 그럼 Book과 publisher가 삭제 될 것이다.

    publisher가 여러개의 연관관계를 맺고 있으면 어떻게 될까?

  • 먼저 Data.sql에 insert문을 추가하자.

    insert into publisher(`id`,`name`) value (1, '빠른대학');
    insert into book(`id`,`name`,`publisher_id`) values (1,'JPA 작은격차 클래스', 1);
    insert into book(`id`,`name`,`publisher_id`) values (2,'Spring Security 작은격차 클래스', 1);
  • Test

      @Test
      void bookRemoveCascadeTest() {
          bookRepository.deleteById(1L);
    
          System.out.println("books : " + bookRepository.findAll());
          System.out.println("publishers " + publisherRepository.findAll());
    
          bookRepository.findAll().forEach(book -> System.out.println(book.getPublisher()));
      }
  • book이 지워지면서 publisher도 지워지긴 하는데 update문이 실행된다. setter를 통해 연관관계가 제거하려고 null로 주입이된다. 여기서 OrphanRemoval를 사용한다.

OrphanRemoval(고아제거속성)

    @Test
    void bookCascadeTest() {
        Book book = new Book();
        book.setName("JPA 초격차 패키지");

        Publisher publisher = new Publisher();
        publisher.setName("빠른대학");
        book.setPublisher(publisher);
        bookRepository.save(book);

        System.out.println("book : " + bookRepository.findAll());
        System.out.println("publishers : " + publisherRepository.findAll());

        Book book1 = bookRepository.findById(1L).get(); // 학습용 코드 실무에서는 이렇게 안함.
        book1.getPublisher().setName("느린대학");
        bookRepository.save(book1);
        System.out.println("publishers : " + publisherRepository.findAll());

        Book book2 = bookRepository.findById(1L).get();
        bookRepository.delete(book2);

        Book book3 = bookRepository.findById(1L).get();
        book3.setPublisher(null);

        bookRepository.save(book3);

        System.out.println("books : " + bookRepository.findAll());
        System.out.println("publishers : " + publisherRepository.findAll());
        System.out.println("book3 : " + bookRepository.findById(1L).get().getPublisher());
    }
  • book3.setPublisher(null); 하는거 처럼 하면 연관관계를 끊는게 된다.
  • 하지만 연관관계를 지웠지만 Publisher는 존재한다. 이때 OrphanRemoval(고아제거속성)을 사용한다.
  • cascade는 말 그대로 상위객체가 remove하게되면 포함하고 있는 객체의 해당 영속성 이벤트를 전파헤서 하위 Entity까지 remove해준다. 하지만 연관관계가 끊어지면 Remove 이벤트가 실행되지않는다. 단순히 set null 할때 orphanRemoval=false하면된다. 만약 OrphanRemoval=true로 하면 null값으로 변경되면서 그 하위 Entity까지 삭제가 된다. 한번 걸어보자.
      @OneToMany(orphanRemoval = true)
      @JoinColumn(name = "publisher_id")
      @ToString.Exclude
      private List<Book> books = new ArrayList<>();

softDelete

  • Cascade와 상관없지만 Delete에 대한 꿀팁이다.

  • 상용서비스에서는 일반적으로 Data를 Delete 쿼리를 사용하는 경우는 많이 없다. 물론 법적요건때문에 회원을 delete하긴하는데 일반적으로 일종의 flag를 이용하여 "지웠다" 라고 인식하게만 한다.

  • 예를 들면 private boolean deleted; 으로 하여 deleted가 true가 되면 삭제된 데이터가 되는 것이다.

    예를 들어
    insert into book(`id`,`name`,`publisher_id`, `deleted`) values (1,'JPA 작은격차 클래스', 1,false);
    insert into book(`id`,`name`,`publisher_id`, `deleted`) values (2,'Spring Security 작은격차 클래스', 1,false);
    insert into book(`id`,`name`,`publisher_id`, `deleted`) values (3,'SpringBoot 하나인 클래스', 1,true); //삭제된 data
  • 그럼 조회를 했을때 나오면 안된다.

    public interface BookRepository extends JpaRepository<Book, Long> {
      List<Book> findByCategoryIsNull();
    }
      @Test
      void softDelete(){
          bookRepository.findAll().forEach(System.out::println);
          System.out.println(bookRepository.findById(3L));
    
          bookRepository.findByCategoryIsNull().forEach(System.out::println);
      }
  • 위 Test에서 deleted가 true이면 결과값이 나오면 안된다. 이럴때는 어떻게 할까?

    public interface BookRepository extends JpaRepository<Book, Long> {
      ...
      ...
      ...
      List<Book> findAllByDeletedFalse();
      List<Book> findByCategoryIsNullAndDeletedFalse();
    }
  • BookRepository에 findAllByDeletedFalse() Deleted가 False인값만 부르는 메서드를 만들어 써야한다.

  • 그럼 항상 이렇게 DeletedFalse 메서드를 계속 만들어 줘야할까? 아래방법으로 하면 된다.

@Where(clause = "deleted =false")
public class Book extends BaseEntity {
}
  • 이렇게 Where 조건을 달면 Entity에 관한 모든쿼리에 Where deleted =false가 붙어서 나온다.

Cascade와 OrphanRemoval 그리고 flag를 이용한 delete를 배웠다. 이중 flag를 이용한 delete는 꿀팁이다!!!.

'Dev > JPA' 카테고리의 다른 글

JPA Native Query  (0) 2022.03.02
JPA @Query  (0) 2022.03.02
JPA에서 Transaction 활용하기  (0) 2022.03.02
JPA Cascade  (0) 2022.03.02
JPA에서 Transaction의 전파  (0) 2022.03.02