ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JPA Cascade, OrphanRemoval
    백엔드 2023. 9. 4. 18:27
    728x90
    반응형
    SMALL

    Cascade는 영속성 전이라고 합니다. 객체에서 다음 객체로 영속성을 보내준다는 의미입니다.

    Cascade는 연관관계가 있을 경우 설정할 수 있습니다.

     

     

    CascadeType

    public enum CascadeType { 
    
        /** Cascade all operations */
        ALL, 
    
        /** Cascade persist operation */
        PERSIST, 
    
        /** Cascade merge operation */
        MERGE, 
    
        /** Cascade remove operation */
        REMOVE,
    
        /** Cascade refresh operation */
        REFRESH,
    
        /**
         * Cascade detach operation
         *
         * @since 2.0
         * 
         */   
        DETACH
    }

    Cascade란 Entity가 해당 영역으로 전이가 일어날 경우 포함하고 있는 릴레이션 엔티티에도 함께 전이를 일으키는 것을 말합니다.

     

     

    예시에 사용될 Entity

    Book

    @Entity
    public class Book{
        ...
        @ManyToOne
        @ToString.Exclude
        private Publisher publisher;
    }

    Publisher

    @Entity
    public class Publisher{
        ...
        @OneToMany
        @JoinColumn(name = "publisher_id")
        private List<Book> books = new ArrayList<>();
    }

    위의 두 Entity를 통해 test해보겠습니다.

     

     

    test1

    @Test
    void test(){
        Book book1 = new Book();
        book1.setName("book1");
        
        Publisher publisher1 = new Publisher();
        publisher1.setName("publisher1");
        
        book1.setPublisher(publisher1);
        bookRespoitory.save(book1);
        
        System.out.println(bookRepository.findAll());
        System.out.println(publisherRepository.findAll());
    }

    위와 같은 테스트를 진행할 경우 publisher1이 DB에 저장되지 않은 자바 객체 상태이기 때문에 book1과 연관관계를 맺어줄 수 없습니다.

     

     

    test1 (cascade 속성에 PERSIST 추가)

    Book Entity를 영속화할 때 연관 관계를 맺은 Publisher Entity에서도 영속화가 진행될 수 있도록 Cascade 설정을 해주도록 하겠습니다.

    @Entity
    public class Book{
        ...
        @ManyToOne(cascade = CascadeType.PERSIST)
        @ToString.Exclude
        private Publisher publisher;
    }

    PERSIST 타입은 Book Entity가 insert 될 경우 cascade 설정을 추가해준 연관관계인 Publisher Entity도 같이 persist 전이가 일어나도록 해줍니다.

     

     

    test2

    @Test
    void test(){
        Book book1 = new Book();
        book1.setName("book1");
        
        Publisher publisher1 = new Publisher();
        publisher1.setName("publisher1");
        
        book1.setPublisher(publisher1);
        bookRespoitory.save(book1);
        
        Book book2 = bookRepository.findById(1L).get();
        book2.getPublisher().setName("publisher2");
        
        bookRepository.save(book2);
        
        System.out.println(bookRepository.findAll());
        System.out.println(publisherRepository.findAll());
    }

    위와 같이 테스트를 실행시킬 경우 book1과 연관 관계인 Publisher Entity인 publisher1의 이름을 publisher2로 변경을 했지만, 실제 Publisher Entity에는 반영이 되지 않은 것을 확인할 수 있습니다.

     

     

    test2 (cascade 속성에 MERGE 추가)

    @Entity
    public class Book{
        ...
        @ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
        @ToString.Exclude
        private Publisher publisher;
    }

    cascade 속성은 리스트로 받을 수 있으므로 설정값들을 여러개 둘 수 있습니다.

    MERGE에 대한 속성을 추가해주면 해당 Entity에 update가 될 경우 cascade 속성이 추가된 연관 관계 Entity에도 merge 전이가 일어나게 되어 Publisher Entity의 name이 수정된 것을 확인할 수 있습니다.

     

     

    cascade의 다른 속성들도 Entity에 영속성의 변화가 생길 경우 연관 관계의 Entity에도 영속성 전이를 일으킬지 지정해주게 됩니다.

    cascade의 default는 아무것도 설정되지 않은 것으로, 설정하지 않으면 연관 관계의 Entity에는 어떠한 영속성 전이도 발생하지 않습니다.

     

     

     

    CascadeType.REMOVE

    @Test
    void test(){
        Book book1 = new Book();
        book1.setName("book1");
        
        Publisher publisher1 = new Publisher();
        publisher1.setName("publisher1");
        
        book1.setPublisher(publisher1);
        bookRespoitory.save(book1);
        
        
        bookRepository.deleteById(1L); // delete
        
        
        System.out.println(bookRepository.findAll());
        System.out.println(publisherRepository.findAll());
    }

    위와 같이 remove 할 경우 book에 대한 Entity만 삭제되고 Publisher Entity는 삭제되지 않습니다.

     

    이 때, cascade 설정에 CascadeType.REMOVE를 추가해준다면 Book Entity 삭제 시 영속성이 전이되어 Publisher Entity도 삭제됩니다.

     

     

    여기서, 두 개의 book과 하나의 publisher가 연관관계를 맺고 있다고 할 때, 하나의 book이 delete되면 publisher에 전이되는 영속성은 어떻게 처리되는지 확인해보도록 하겠습니다.

     

     

    test에 사용할 Entity 추가

    @Test
    void test(){
        Book book1 = new Book();
        book1.setName("book1");
        
        Book book2 = new Book();
        book2.setName("book2");
        
        Publisher publisher1 = new Publisher();
        publisher1.setName("publisher1");
        
        book1.setPublisher(publisher1);
        bookRespoitory.save(book1);
        
        book2.setPublisher(publisher1);
        bookRespoitory.save(book2);
    }

    아래 테스트 상에서 Entity 추가해주는 코드는 작성하지 않도록 하겠습니다.

    Book Entity에서 Publisher 연관관계에 대해 CascadeType.REMOVE 설정이 추가된 상태로 가정하겠습니다.

     

     

    test1

    @test
    void test(){
        ... // Entity 추가 코드
        
        bookRepository.deleteById(1L);
        
        System.out.println(bookRepository.findAll());
        System.out.println(publisherRepository.findAll());
    }

    위의 테스트를 실행할 경우 Book Entity의 delete를 통해 Publisher Entity 또한 삭제됩니다. 그러므로 book2 Entity만 남게 됩니다.

    book2와 연관관계를 맺었던 Publisher는 null로 수정됩니다.

     

     

    test2 (OrphanRemoval)

    OrphanRemoval을 설명하기에 앞서 연관 관계를 제거하는 방법을 먼저 살펴보도록 하겠습니다.

    연관 관계를 제거하기 위해서는 setter를 통해 null을 넣으면 됩니다. (위의 테스트에서도 update를 통해 publisher가 null로 수정됐습니다.)

    setter를 통해 연관관계를 끊은 경우 연관관계가 끊긴 Entity, 즉, book1.setPublisher(null)로 book과 publisher의 연관 관계를 끊을 경우 publisher Entity는 없어지지 않고 존재하게 됩니다.

     

     

    CascadeType.REMOVE는 상위 객체가 remove 영속성 전이를 일으키면 연관 관계의 Entity까지 remove 해주는 옵션입니다. 만약 상위Entity가 하위 Entity의 연관 관계를 끊을 경우, 연관 관계가 사라진 하위 Entity는 지워지지 않습니다.

    OrphanRemoval 또한 상위 Entity가 remove 될 경우 하위 Entity 또한 remove됩니다. 만약 상위 Entity가 하위 Entity의 연관 관계를 끊을 경우, 연관 관계가 없어진 하위 Entity를 고아로 취급하고 하위 Entity를 제거하는 속성입니다.

     

     

     

    예시)

    Team

    @Entity
    public class Team{
        ...
        
        @OneToMany(
            mappedBy="team",
            cascade = CascadeType.REMOVE,
            orphanRemoval = true
        )
        List<Member> members = new ArrayList<>();
    }

    Member

    @Entity
    public class Member{
        ...
        
        @ManyToOne
        @JoinColumn
        private Team team;
    }

    Test

    @Test
    void test1(){
        Member member1 = new Member();
        Member member2 = new Member();
        
        Team team1 = new Team();
        
        team1.addMember(member1);
        team1.addMember(member2);
        
        teamRepository.save(team1);
        
        teamRepository.delete(team1);
    }
    
    @Test
    void test2(){
        Member member1 = new Member();
        Member member2 = new Member();
        
        Team team1 = new Team();
        
        team1.addMember(member1);
        team1.addMember(member2);
        
        teamRepository.save(team1);
        
        team1.getMembers().remove(0);
    }

    test1에서 team1이 삭제됨에 따라 team1에 의해 영속성 전이된 member1과 member2도 같이 삭제되게 됩니다.

     

    test2에서 team1과 member1의 연관 관계가 끊어지게 되면 team1의 orphanRemoval속성에 의해 연관 관계를 잃은 member1은 고아로 취급되고 제거됩니다.

    만약 orphanRemoval 속성을 설정하지 않았다면 team1과 member1의 연관 관계가 끊어지더라도 member1은 제거되지 않습니다.

    반응형
    LIST

    '백엔드' 카테고리의 다른 글

    Jpa Embedded  (0) 2023.09.06
    JPA @Query, Native Query, Converter  (0) 2023.09.05
    Spring IOC, DI, AOP  (0) 2023.08.26
    JPA Transaction  (0) 2023.08.25
    영속성 컨텍스트, Entity Cache, Entity Lifecycle  (2) 2023.08.24

    댓글

Designed by Tistory.