[JPA] JPA Cascade (영속성 전이)

jpa, spring

Cascade (영속성 전이) 란? #

CascadeType #

Cascade 적용 #

Parent - Child 관계에 있는 도메인에 적용할 수 있다.

Food 도메인과 Company 도메인을 만들어 적용해보자.

...
@Entity
public class Company extends BaseTimeEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long companyId;

    private String name;

    private String image;
    
    @OneToMany(fetch = FetchType.EAGER, mappedBy = "company")
    @Builder.Default
    @ToString.Exclude
    private List<Food> foods = new ArrayList<>();

    public void addFood(Food food) {
        this.getFoods().add(food);
        food.setCompany(this);
    }
}
...
@Entity
public class Food extends BaseTimeEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long foodId;

    private String name;

    private String image;
    
    @Enumerated(EnumType.STRING)
    private Category category;

    @ManyToOne
    @JoinColumn(foreignKey = @ForeignKey(name = "foodId"))
    Company company;
}

그리고 간단하게 테스트 환경을 만든다.

@Transactional
@SpringBootTest
public class CompanyTest {

    @Autowired private CompanyRepository companyRepository;
    @Autowired private FoodRepository foodRepository;

    @Test
    void testAddFood() {
        Food food = new Food();
        food.setName("testFood");

        Company company = new Company();
        company.setName("testCompany");
        company.setImage("logoImage");
        company.addFood(food);
        companyRepository.save(company);

        System.out.println(">>> " + companyRepository.findAll());
        System.out.println(">>> " + foodRepository.findAll());
    }
}

위와 같이 테스트를 실행하면 당연하게도 Company Repository 에만 데이터가 저장되어 있고, FoodRepository 에는 아무것도 없다.

>>> [Company(super=BaseTimeEntity(createdAt=2022-02-09T16:59:29.557061100, updatedAt=2022-02-09T16:59:29.557061100), companyId=1, name=testCompany, image=logoImage)]
>>> []

하지만 Company 도메인에 cascade 옵션을 준다면

...
@Entity
public class Company extends BaseTimeEntity {
    ...

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "company", cascade = CascadeType.ALL)
    @Builder.Default
    @ToString.Exclude
    private List<Food> foods = new ArrayList<>();
...
>>> [Company(super=BaseTimeEntity(createdAt=2022-02-09T17:02:26.854705300, updatedAt=2022-02-09T17:02:26.854705300), companyId=1, name=testCompany, image=logoImage)]
>>> [Food(super=BaseTimeEntity(createdAt=2022-02-09T17:02:27.010724200, updatedAt=2022-02-09T17:02:27.010724200), foodId=1, name=testFood, image=null, category=null, company=Company(super=BaseTimeEntity(createdAt=2022-02-09T17:02:26.854705300, updatedAt=2022-02-09T17:02:26.854705300), companyId=1, name=testCompany, image=logoImage))]

위의 결과값 처럼 자식객체(Food) 또한 함께 persist 되면서 저장된다.


다른 예제 #

아래와 같은 테스트가 있다.

@Test
@Transactional
void bookCascadeTest() {
    Book book = new Book();
    book.setName("JPA 초격차 패키지");
    bookRepository.save(book); // 영속화

    Publisher publisher = new Publisher();
    publisher.setName("FastCampus");
    publisherRepository.save(publisher); // 영속화

    // 연관관계 만들기
    book.setPublisher(publisher); 
    bookRepository.save(book); // 영속화

    publisher.addBook(book);
    publisherRepository.save(publisher); // 영속화

    System.out.println("books : " + bookRepository.findAll());
    System.out.println("publishers : " + publisherRepository.findAll());
}

위와 같은 경우에 영속화를 4번이나 하는것은 비효율적이기 때문에 이때 cascade를 사용한다.

public class Book extends BaseEntity {
    ...
    @ManyToOne(cascade = CascadeType.PERSIST)
    @ToString.Exclude
    private Publisher publisher;
    ...
}
@Test
@Transactional // lazy initialization exception
void bookCascadeTest() {
    Book book = new Book();
    book.setName("JPA 초격차 패키지");

    Publisher publisher = new Publisher();
    publisher.setName("FastCampus");

    book.setPublisher(publisher); // 연관관계 만들기
    bookRepository.save(book);
    
    System.out.println("books : " + bookRepository.findAll());
    System.out.println("publishers : " + publisherRepository.findAll());
    }

참조 #