Dev/JPA

JPA Cascade

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

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