영속성 전이
: 하나의 객체에서 다음 객체에 영속성을 흐려 보내는 것
Cascade의 경우, 연관관계가 있는 엔티티에(@OneToMany, @OneToOne...) 있는 경우에 CascadeType에 사용 가능.
CascadeType 클래스 (Enum 타입)
- ALL : 모든 엔티티에 영속성 전이.
- PERSIST : 엔티티 저장시 영속성 전이
- MERGE : 엔티티 업데이트 시 영속성 전이
- REMOVE : 엔티티 제거 작업 시 영속성 전이
- REFRESH : 엔티티의 영속성을 재로딩.
- DETACH : 영속성을 관리하지 않고 영속성으로부터 분리하는 속성.
해당 영역으로 전이가 일어날 때 릴레이션 엔티티에도 함께 전이를 일이킬지에 대한 속성 값
예제 1. CascadeType.PERSIST
CascadeType.PERSIST
@Test
void bookCascadeTest(){
Book book = new Book();
book.setName("JPA Cascade");
// 잦은 영속성 작업은 불필요로 인한 주석 처리
//bookRepository.save(book); // 데이터 영속화 작업
Publisher publisher = new Publisher();
publisher.setName("Publisher JPA");
// 잦은 영속성 작업은 불필요로 인한 주석 처리
//publisherRepository.save(publisher);
book.setPublisher(publisher); //Book과 Publisher 의 연관관계 적용
bookRepository.save(book);
//publisher.getBooks().add(book); // setBook의 경우 모든 리스트를 넣어줘야하므로 Book의 정보를 가져와서 add
//publisher.addBook(book); // getBook.add하는 방법보다 더 가독성 있는 코드
//publisherRepository.save(publisher);
System.out.println("all books : "+bookRepository.findAll());
System.out.println("all publishers : "+publisherRepository.findAll());
}
ErrorMessage
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing
해결 방법 : 영속성 전이 CascadeType.PERSIST 적용 (Book.java)
@ManyToOne(cascade = CascadeType.PERSIST)
@ToString.Exclude
private Publisher publisher;
예제 2. CascadeType.MERGE 및 Cascade 다중 설정
// persist가 아닌 merge의 대한 이벤트 발생. publisher의 cascader 는 persist만 설정.
Book book1 = bookRepository.findById(1L).get();
book1.getPublisher().setName("Cascade Flow");
bookRepository.save(book1);
System.out.println("publishers : "+publisherRepository.findAll());
}
결과 : 데이터가 변경되지 않음. name의 값이 그대로.
publishers : [Publisher(super=BaseEntity(createdAt=2023-05-11T00:31:10.585473, updatedAt=2023-05-11T00:31:10.585473), id=1, name=Publisher JPA)]
merge의 경우에도 Cascade 실행하기 위해 속성 추가
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@ToString.Exclude
private Publisher publisher;
결과 : update가 실행되어 name값이 변경.
publishers : [Publisher(super=BaseEntity(createdAt=2023-05-11T00:35:55.058285, updatedAt=2023-05-11T00:35:55.321926), id=1, name=Cascade Flow)]
CascadeType.REMOVE
- 엔티티 제거 작업 시 영속성 전이
- CascadeType.REMOVE와 OrphanRemoval 는 같지않고 REMOVE는 부모 엔티티를 통해 자식 엔티티를 삭제하면 둘 사이의 연관관계가 끊어지고 자식 엔티티는 그대로 남는다
Book.java (부모) - publisher (자식)
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
@ToString.Exclude
private Publisher publisher;
삭제 처리
@Test
void bookRemoveCascadeTest1(){
Book book = new Book();
book.setName("JPA Cascade");
Publisher publisher = new Publisher();
publisher.setName("Publisher JPA");
book.setPublisher(publisher);
bookRepository.save(book);
System.out.println("all books : "+bookRepository.findAll());
System.out.println("all publishers : "+publisherRepository.findAll());
Book book1 = bookRepository.findById(1L).get();
book1.getPublisher().setName("Cascade Flow");
bookRepository.save(book1);
System.out.println("publishers : "+publisherRepository.findAll());
Book book2 = bookRepository.findById(1L).get();
bookRepository.delete(book2);
System.out.println("books delete : "+bookRepository.findAll());
System.out.println("publisher delete : "+publisherRepository.findAll());
}
제거시 영속성 전이되어 모두 삭제
(...중략)
books delete : []
Hibernate:
select
publisher0_.id as id1_4_,
publisher0_.created_at as created_2_4_,
publisher0_.updated_at as updated_3_4_,
publisher0_.name as name4_4_
from
publisher publisher0_
publisher delete : []
OrphanRemoval 고아제거속성
: 고아제거속성은 연관관계가 없는 entity를 제거하는 속성.
부모 엔티티가 삭제되면 연관관계가 맺어진 자식 엔티티는 모두 삭제된다.
@연관관계(orphanRemoval = true)를 설정해준다. default 는 false
Pulisher.java
@OneToMany(orphanRemoval = true)
@JoinColumn(name = "publisher_id")
@ToString.Exclude
private List<Book> books = new ArrayList<>();
@Test
void bookRemoveCascadeTest3(){
Book book = new Book();
book.setName("JPA Cascade");
Publisher publisher = new Publisher();
publisher.setName("Publisher JPA");
book.setPublisher(publisher);
bookRepository.save(book);
System.out.println("all books : "+bookRepository.findAll());
System.out.println("all publishers : "+publisherRepository.findAll());
Book book1 = bookRepository.findById(1L).get();
book1.getPublisher().setName("Cascade Flow");
bookRepository.save(book1);
System.out.println("publishers : "+publisherRepository.findAll());
Book book3 = bookRepository.findById(1L).get();
book3.setPublisher(null); // null
bookRepository.save(book3);
System.out.println("books delete : "+bookRepository.findAll());
System.out.println("publisher delete : "+publisherRepository.findAll());
System.out.println("book3 publisher : "+bookRepository.findById(1L).get().getPublisher());
}
book3 제거됨
books delete : [Book(super=BaseEntity(createdAt=2023-05-12T01:01:55.639303, updatedAt=2023-05-12T01:01:56.542801), id=1, name=jpa package, category=null, authorId=null, deleted=false), Book(super=BaseEntity(createdAt=2023-05-12T01:01:55.641752, updatedAt=2023-05-12T01:01:55.641752), id=2, name=spring package, category=null, authorId=null, deleted=false), Book(super=BaseEntity(createdAt=2023-05-12T01:01:56.192956, updatedAt=2023-05-12T01:01:56.192956), id=4, name=JPA Cascade, category=null, authorId=null, deleted=false)]
publisher delete : [Publisher(super=BaseEntity(createdAt=2023-05-12T01:01:55.633548, updatedAt=2023-05-12T01:01:56.492014), id=1, name=Cascade Flow), Publisher(super=BaseEntity(createdAt=2023-05-12T01:01:56.244489, updatedAt=2023-05-12T01:01:56.244489), id=2, name=Publisher JPA)]
book3 publisher : null
'Spring > 1-3. JPA' 카테고리의 다른 글
낙관적 락(Optimistic Lock) 과 비관적 락(Pessimistic Lock) (0) | 2024.11.13 |
---|---|
(9. Spring Data JPA) @Query 와 Native Query (0) | 2023.05.23 |
(7. Spring Data JPA) Transaction Manager (0) | 2023.05.10 |
(6. Spring Data JPA) 영속성 컨텍스트 Persistence Context (0) | 2023.05.06 |
(5. Spring Data JPA) Entity Listener 활용 (1) | 2022.10.18 |