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
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는 꿀팁이다!!!.