아카이브

[스프링 데이터 JPA] JPA 프로그래밍 5. Cascade 본문

Spring/스프링 데이터 JPA

[스프링 데이터 JPA] JPA 프로그래밍 5. Cascade

주멘이 2021. 1. 10. 22:09

Cascade

엔티티의 상태변화를 전파시키는 옵션

엔티티 상태

Transient: JPA가 모르는 상태

  • 데이터베이스에 들어갈지 안 들어갈지도 전혀 모르는 상태
  • new Object()
  • save()를 호출하기 전

Persistent: JPA가 관리중인 상태

  • 1차 캐시: Persistent Context(EntityManager, Session)에 인스턴스를 넣은 것
    • 아직 저장이 되지 않은 상태에서 다시 인스턴스를 달라고 하면 이미 객체가 있으므로 데이터베이스에 가지 않고 캐시 하고 있는 것을 줌
    • save()를 호출한다고 바로 Insert 쿼리가 실행되는 것은 아니다.
    • save()를 호출하면 PersistentContext에 Instance를 넣어서 1차 캐싱한다.
  • Dirty Checking: 이 객체의 변경사항을 계속 감지
  • Write Behind: 객체의 상태의 변화를 데이터베이스에 최대한 늦게 가장 필요한 시점에 적용을 함
    • 원래 가지고 있던 객체의 값과 동일한경우 변경사항을 적용하지 않음
    • 변경사항을 계속 감지하다가 처음이랑 똑같으면 update 하지 않는다.

Detached: JPA가 더이상 관리하지 않는 상태

  • Transaction이 끝났을 때 DB에 실제로 저장되고 Session이 종료된 상태

Removed: JPA가 관리하긴 하지만 삭제하기로 한 상태

  • delete() 호출

 

AccountStudyRunner 클래스에서 1차 캐시와 Write Behind 테스트

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        /*
         * Transient 상태 : JPA가 아무것도 모르는 상태
         * */
        Study study = new Study();
        study.setName("Spring Data JPA");

        Account account = new Account();
        account.setUsername("WON");
        account.setPassword("hibernate");
        account.addStudy(study); /* 양방향 관계 설정하기 */

        Session session = entityManager.unwrap(Session.class);  /* 하이버네이트의 가장 핵심적인 API는 Session이다.*/
        session.save(post);

        /*
         * Persistent 상태 : JPA가 관리중인 상 (1차 캐시)
         * save를 호출한다고 해서 바로 INSERT 쿼리가 발생하는 것은 아니다.
         * save를 호출하면 1차 캐시 : PersistenceContext에 이 instance를 넣어서 캐싱한다.
         * */

        session.save(account);
        session.save(study);

        Account load = session.load(Account.class, account.getId());
        load.setUsername("WON 1");
        load.setUsername("WON 2");
        load.setUsername("WON");


        /*
         * 이렇게 했는데도 update 쿼리가 실행되지 않는다.
         * 1. Dirty Checking (이 객체의 변경사항을 감시한다)
         * 2. Write Behind (객체의 변경사항을 최대한 늦은 시점에 반영한다.)
         *
         * 계속 감시하다 보니까 변경사항이 처음이랑 똑같다.(WON -> WON 1 -> WON 2 -> WON)
         * update 하지 않겠다.
         * */

        System.out.println("========== 현재 상태에서는 캐싱된 값을 보여주므로 이게 끝나고 INSERT가 동작한다. ==========");
        System.out.println("========== 실제 INSERT는 트랜잭션이 끝날때 발생한다. ==========");
        System.out.println(load.toString());

    }
}

 

Post, Comment Cascade 테스트하기

Parent인 Post 클래스 생성

@Entity
public class Post {

    @Id @GeneratedValue
    private Long id;
    private String title;

    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL)    // 전파하라
    private Set<Comment> comments = new HashSet<>();
    public void addComment(Comment comment) {
        this.getComments().add(comment);
        comment.setPost(this);
    }
}

Child인 Comment 클래스 생성

@Entity
public class Comment {

    @Id @GeneratedValue
    private Long id;
    private String comment;

    @ManyToOne
    private Post post;
}

 

Post-Comment CascadeType.ALL 일 때 save() 테스트 

    @Override
    public void run(ApplicationArguments args) throws Exception {

        //Post-Comment CASCADE 예제
        Post post = new Post();
        post.setTitle("Spring DATA JPA 포스트 타이틀");

        List<Comment> comments = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Comment comment = new Comment();
            comment.setComment("Comment " + i);
            post.addComment(comment);
        }

        Session session = entityManager.unwrap(Session.class);  /* 하이버네이트의 가장 핵심적인 API는 Session이다.*/
        session.save(post);
        
        // CascadeType.ALL 이므로 post를 save()할때, comment들도 함께 전파되어 들어간다.
    }

보통 cascade=CascadeType.ALL 로 모두 전파하도록 설정한다.