영어로는 작은 폭포를 의미 -> 단계별로 전이가 발생
해당 영역으로 전이가 일어날 시 포함하고 있는 릴레이션 엔티티에도 함께 전이를 일으킬지 설정
하는 값
@OneToOne, @OneToMany, @ManyToOne 연관 관계가 있는 어노테이션
에 cascade() 지원
CascadeType.java 에는 ALL, PERSIST, MERGE,REMOVE, REFRESH, DETACH속성 지원
ALL
→ 모든 Cascade 적용PERSIST
→ 엔티티를 영속화할 때, 연관된 엔티티도 함께 유지MERGE
→ 엔티티 상태를 병합(Merge)할 때, 연관된 엔티티도 모두 병합REMOVE
→ 엔티티를 제거할 때, 연관된 엔티티도 모두 제거(주의해야할 옵션)REFRESH
→ 상위 엔티티를 새로고침(Refresh)할 때, 연관된 엔티티도 모두 새로고침DETACH
→ 부모 엔티티를 detach() 수행하면, 연관 엔티티도 detach()JPA는 자바코드를 SQL쿼리로 번역해주는 ORM
-> save() 즉 영속화를 자주 사용함으로서 자바관점에선 객체지향적인 코드가 아니다
@Test
void bookCascadeTest(){
Book book = new Book();
book.setName("JPA 책");
bookRepository.save(book); //Book Entity 영속화
Publisher publisher = new Publisher();
publisher.setName("테스트 출판사");
publisherRepository.save(publisher); //Publisher Entity 영속화
book.setPublisher(publisher); //Book Entity에 영속화된 Publisher 수정
bookRepository.save(book);
// call by reference(자바) , setter 가 직관적, 따라서 add Book 메서드 생성하는 것이 좋음
// publisher.getBook().add(book);
publisher.addBook(book); //Publisher Entity에 영속화된 Book 수정
publisherRepository.save(publisher);
System.out.println("books : " + bookRepository.findAll());
System.out.println("publishers : " + publisherRepository.findAll());
}
위 코드는 디비에 저장, 디비에 저장 후 연관관계를 맺어 주었음
영속성 전이를 이용해 객체 중심의 코드 수정이 가능
Book.java
@ManyToOne(cascade = {CascadeType.PERSIST, CaseCadeType.MERGE})
@ToString.Exclude
private Publisher publisher;
위 코드의 의미: Book을 persist할때 연관성이 잇는 publisher도 같이 persist 해라
@SpringBootTest
class BookRepositoryTest {
...
@Test
void bookCascadeTest(){
Book book = new Book();
book.setName("JPA 책");
Publisher publisher = new Publisher();
publisher.setName("나는 출판사");
//비영속화 상태에서 엔티티간 연관관계 맺기가 안됌 (cascade로 연관관계 맺음)
book.setPublisher(publisher);
bookRepository.save(book);
System.out.println("books : " + bookRepository.findAll());
System.out.println("publishers : " + publisherRepository.findAll());
--- 이까지 CasCadeType : Persist로 해결 ---
Book book1 = bookRepository.findById(1L).get();
book1.getPublisher().setName("남의 출판사");
bookRepository.save(book1);
System.out.println("publishers : " + publisherRepository.findAll());
--- merge 안할 시 이름이 변경이 안됨 ---
--- update 시 merge에 대한 이벤트가 발생하여 이를 위한 영속성 전이를 해주어야 함 ---
}
}
books : [Book(super=BaseEntity(createdAt=2022-11-22T16:49:09.267504, updatedAt=2022-11-22T16:49:09.267504), id=1, name=jpa 공부하기, category=null, authorId=null)]
publishers : [Publisher(super=BaseEntity(createdAt=2022-11-22T16:49:09.271674, updatedAt=2022-11-22T16:49:09.271674), id=1, name=나는출판사)]
publishers : [Publisher(super=BaseEntity(createdAt=2022-11-22T16:49:09.271674, updatedAt=2022-11-22T16:49:09.349512), id=1, name=남의 출판사)]
Default 값으로는 아무런 Cascade가 일어나지 않음
Book.java
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Book extends BaseEntity{
...
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
@ToString.Exclude
private Publisher publisher;
...
}
book만 제거하더라도 연관관계에 있는 Publisher 까지 같이 제거됨
@SpringBootTest
class BookRepositoryTest {
@Autowired
private BookRepository bookRepository;
@Autowired
private PublisherRepository publisherRepository;
...
@Test
void bookRemoveCascadeTest(){
bookRepository.deleteById(1L); //cascade 삭제속성으로 연관엔티티 관계제거
System.out.println("books : " + bookRepository.findAll());
System.out.println("publishers : " + publisherRepository.findAll());
bookRepository.findAll().forEach(book -> System.out.println(book.getPublisher()));
}
}
하지만 book과 Publisher는 N:1 관계-> 하나의 출판사가 여러가지 책 만듬
data.sql(위 상황에 이용)
insert into publisher(`id`, `name`) values (1, '패트스캠퍼스');
insert into book(`id`, `name`, `publisher_id`) values(1, 'JPA 초격차 패키지', 1);
insert into book(`id`, `name`, `publisher_id`) values(2, '스프링 시큐리티 패키지', 1);
이대로 각각의 리포지토리 프린트시 ToString.Exclude 때문에 연관관계는 표시 안됨
연관관계가 없는 엔티티를 제거하는 속성
연관관계를 제거하는 방법은 setter에 null값을 주입
Hibernate:
update
book_and_author
set
book_id=null
where
book_id=?
Hibernate:
update
review
set
book_id=null
where
book_id=?
Hibernate:
update
book
set
publisher_id=null
where
publisher_id=?
Hibernate:
delete
from
book
where
id=?
Hibernate:
delete
from
publisher
where
id=?
CascadeType.REMOVE, orphanRemoval의 차이
CascadeType.REMOVE: @ManyToOne(cascade = {CascadeType.REMOVE}) 설정
orphanRemoval: @OneToMany(orphanRemoval = true) 설정
Cascade는 말그대로 상위 객체가 리무브 액션을 취하고있는 포함하고 잇는 객체에 해당 영속성 이벤트를 전파해서 하위 엔티티까지 리무브 시켜주는것
-> 연관관계가 끊어질때(set null) 리무브 이벤트가 발생하지는 않음
연관관계가 없는 데이터를 살려둘려면 orphanRemoval = false
사용
-> 고아 객체 제거하려면 true
공식 문서
For orphan removal : If you invoke setOrders(null), the related Order entities will be removed in the db automatically
For remove Cascade : f you invoke setOrders(null), the related Order entities will Not be removed in db automatically
현업에서 자주 사용하는 방법
이 전 방법들은 delete로 삭제하지만 현업에선 잘 사용하지 않음
-> 삭제 flag 컬럼을 생성 후 조건으로 검색
아래의 예시는 deleted flag 컬럼을 설정하고 true
는 삭제로 판단
-> java boolean타입은 db에 0과 1로 표시 (0: false, 1: true)
data.sql
insert into book(`id`, `name`, `publisher_id`, `deleted`) values(1, 'JPA 초격자 패키지', 1, false);
insert into book(`id`, `name`, `publisher_id`, `deleted`) values(2, 'Spring', 1, false);
insert into book(`id`, `name`, `publisher_id`, `deleted`) values(3, 'Spring Security', 1, true);
Book.java
...
public class Book extends BaseEntity{
...
private boolean deleted;
}
BookRepository.java
@Repository
public interface BookRepository extends JpaRepository<Book, Long> {
...
List<Book> findAllByDeletedFalse();
List<Book> findByCategoryIsNullAndDeletedFalse();
}
test 코드
@SpringBootTest
class BookRepositoryTest {
...
@Test
void softDelete() {
bookRepository.findAllByDeletedFalse().forEach(System.out::println);
bookRepository.findByCategoryIsNullAndDeletedFalse().forEach(System.out::println);
}
}
하지만 이 경우 DeletedFalse를 누락할 시 큰 Side Effect를 불러 올 수 있음
이 경우 Entity에 @Where를 추가하고 조건문 생성
@Where(clause = "deleted = false")
public class Book extends BaseEntity{
...
}
작성한 조건문은 항상 추가되어 쿼리문이 실행 됨
Hibernate:
select
book0_.id as id1_2_,
book0_.created_at as created_2_2_,
book0_.updated_at as updated_3_2_,
book0_.author_id as author_i4_2_,
book0_.category as category5_2_,
book0_.deleted as deleted6_2_,
book0_.name as name7_2_,
book0_.publisher_id as publishe8_2_
from
book book0_
where
(
book0_.deleted = 0
)
Book(super=BaseEntity(createdAt=null, updatedAt=null), id=1, name=JPA 초격차 패키지, category=null, authorId=null, deleted=false)
Book(super=BaseEntity(createdAt=null, updatedAt=null), id=2, name=스프링 시큐리티 패키지, category=null, authorId=null, deleted=false)
자동으로 id=3의 값은 추가되지 않음
DB에는 값이 존재하지만 로직상에서 출력 되지 않게 하는 방법!!