JPA에서 Cascade(영속성 전이) 활용하기
- ALL, PERSIST, MERGE, REMOVE, REFRESH, DETECH
@Test
void bookCascadeTest() {
// 영속성 전이 테스트
// 책 생성 후 저장
Book book = new Book();
book.setName("JPA 초격차 패키지");
bookRepository.save(book);
// 출판사 생성
Publisher publisher =new Publisher();
publisher.setName("우리집");
publisherRepository.save(publisher);
// 책의 출판사를 set
book.setPublisher(publisher);
bookRepository.save(book);
// 출판사의 책을 set
// callByValue callByRef
// publisher는 List이기 때문에 하나의 책(객체)를 불러와서 book으로 넣는다.
// publisher.getBooks().add(book);
publisher.addBook(book); // 위에 코드 보다 조금더 가독성있다.
// 하지만 setter를 사용하여 직관적으로 표현하는게 더 좋다.
// 이런경우에는 Publisher클래스에 addBook()함수를 하나 만드는 것도 좋은 방법이다.
publisherRepository.save(publisher);
System.out.println("book : " + bookRepository.findAll());
System.out.println("publishers : " + publisherRepository.findAll());
}
could not initialize proxy - no Session
Test를 할때 위 Error가 뜨면 2가지 방법이 있다. 자세한 내용은 다음에 다룰것이다.
- noSession이기 때문에 메서드에 @Transactional를 달아주면 된다.
- 또는 엔티티의 변수에 @ToString.Exclude 달아준다.
@OneToMany // 1 : N \ Book : Review @JoinColumn(name = "book_id") //중간 테이블 방지 @ToString.Exclude // 릴레이션은 단방향으로 걸고 ToString은 제외해야한다. private List<Review> reviews = new ArrayList<>();
위 테스트 처럼 연관관계를 맺는게 틀리지는 않았다. 하지만 save(), 즉 영속성을 관리해주는 코드가 하나하나 씩 계속 들어간다. 조금더 자연스럽게 하려면 아래 두 코드는 마지막에 한번만 호출해도 될것이다. 그러기 위해서 영속성 전이, 즉 Cascade를 사용하여 save메서드의 활동을 제거하고 좀더 자바스럽게, 객체에 집중하여 코딩할수 있다.
publisherRepository.save(publisher); bookRepository.save(book);
Cascade 하기전 TEST 코드를 고쳐보자.
@Test void bookCascadeTest() { Book book = new Book(); book.setName("JPA 초격차 학습"); Publisher publisher =new Publisher(); publisher.setName("우리집"); book.setPublisher(publisher); bookRepository.save(book); // publisher.addBook(book); Cascade(영속성전이)할 것 이기 때문에 따로 publisher에 set 하지 않아도 된다. System.out.println("book : " + bookRepository.findAll()); System.out.println("publishers : " + publisherRepository.findAll()); }
object references an unsaved transient instance - save the transient instance before flushing : com.practice.jpa.bookmanager.domain.Book.publisher -> com.practice.jpa.bookmanager.domain.Publisher
- 위 애러가 뜬다. Entity의 레퍼런스가 DB에 저장되지 않고 그냥 자바 객체이기 때문에 저장할수 없다는 거다. book.setPublisher(publisher); 이렇게 연관관계를 맺었는데 Book과 publisher는 영속성관리가 되지않고있기 때문에 연관관계를 맺어주기 힘들다. 앞서 Test할때는 save코드를 중간중간에 넣어서 DB에 저장해주었다. 지금은 Cascade 옵션을 Book클래스에 달아주자.
@ManyToOne(cascade = CascadeType.PERSIST)
@ToString.Exclude
private Publisher publisher;
- Book Entity가 PERSIST 될때, Publisher Entity도 같이 PERSIST 시켜라는 뜻이다.
- 로그를 확인해 보면 book.setPublisher(publisher); 코드가 실행될때 연관관계 맺어지면서 자동으로 insert문이 실행되는걸 볼수 있다.
Hibernate: insert into publisher (created_at, updated_at, name) values (?, ?, ?) Hibernate: insert into book (created_at, updated_at, author_id, category, name, publisher_id) values (?, ?, ?, ?, ?, ?)
- 다음은 book 객체를 불러와서 출판사를 "우리집"에서 "바뀐집"으로 변경해보자.
Book book1 = bookRepository.findById(1L).get();
book1.getPublisher().setName("바뀐집");
bookRepository.save(book1);
System.out.println("publishers : "+publisherRepository.findAll());
- Test 결과를 보면 그대로 "우리집" 일 것이다. 왜냐하면 우리가 해준 Cascade는 Persist할때만 실행되기 때문이다. 그럼 update할 때도 실행되도록 해보자.
@ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE} ) @ToString.Exclude private Publisher publisher;
- 위와 같이 CascadeType.MERGE 추가해 주면 된다.
- 여기서 중요한 것은 Publisher객체를 생성해서 따로 설정하지 않고 Book Entity에 있는 Publisher를 수정하면 수정이 가능하다는 점이다.
- 이와같이 REMOVE, REFRESH, DETECH도 할 수 있다.
- DETECH는 영속성을 관리하지 않겠다는 뜻이다. DETECH하는 시점에 연관관계를 맺고있는 엔티티를 함께 DETECH 하여 영속성 Context를 관리하지 않겠다는 뜻이다.
- REFRESH Entity를 로딩했을때 연관관계있는 Entity도 재로드 하겠다는 옵션이다.
- ALL로 하면 모든 영속성전이하겠다는 것이다.
'Dev > JPA' 카테고리의 다른 글
JPA OrphanRemoval(+@Where) (0) | 2022.03.02 |
---|---|
JPA에서 Transaction 활용하기 (0) | 2022.03.02 |
JPA에서 Transaction의 전파 (0) | 2022.03.02 |
영속성 컨텍스트(Entity LifeCycle) (0) | 2022.03.02 |
영속성 컨텍스트(Entity Cache) (0) | 2022.03.02 |