개발강의정리/Spring

[스프링 데이터 JPA] 2-4. JPA 프로그래밍 5: Entity 상태와 Cascade

nineDeveloper 2019. 11. 22.
728x90

스프링 데이터 JPA

2. 핵심 개념 이해

본격적인 스프링 데이터 JPA 활용법을 학습하기에 앞서, ORM과 JPA에 대한 이론적인 배경을 학습합니다

포스팅 참조 정보

GitHub

공부한 내용은 GitHub에 공부용 Organizations에 정리 하고 있습니다

해당 포스팅에 대한 내용의 GitHub 주소

실습 내용이나 자세한 소스코드는 GitHub에 있습니다
포스팅 내용은 간략하게 추린 핵심 내용만 포스팅되어 있습니다

https://github.com/freespringlecture/spring-data-jpa-study/tree/chap02-04-05_entity_status_cascade

해당 포스팅 참고 인프런 강의

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-jpa/dashboard

실습 환경

  • Java Version: Java 11
  • SpringBoot Version: 2.1.2.RELEASE

4. JPA 프로그래밍 5: 엔티티 상태와 Cascade

Cascade: 엔티티 상태​ 변화를 전파 시키는 옵션

엔티티 상태​가 뭐지

1-3

Transient: JPA가 모르는 상태

데이터베이스에 들어갈지 안들어갈지도 전혀 모르는 상태

new Object()

Persistent: JPA가 관리중인 상태

Persistent 상태가 됐다고 바로 Insert가 발생해서 데이터베이스에 저장하지 않음
Persistent 에서 관리하고 있던 객체가 데이터베이스에 넣는 시점에 데이터를 저장함

Session.save(), Session.get(), Session.load(), Query.iterate()
Session.update(), Session.merge(), Session.saveOrUpdate()

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

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

Transaction 이 끝났을 때 이미 데이터베이스에 저장이되고 Session이 끝난 상태
Session.evict(), Session.clear(), Session.close()

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

Session.delete()

Cascade 상태 테스트

1차 캐쉬에 저장된 인스턴스 가져오는 테스트

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        /** Transient 상태 **/
        Account account = new Account();
        account.setUsername("freelife");
        account.setPassword("hibernate");

        Study study = new Study();
        study.setName("Spring Data JPA");

        account.addStudy(study);

        /** Persistent 상태 **/
        session.save(account);
        session.save(study);

        // 데이터베이스에 가지 않고 이미 1차 캐쉬에 저장된 인스턴스를 가져옴 
        Account freelife = session.load(Account.class, account.getId());
        freelife.setUsername("ironman");
        System.out.println("=====================");
        System.out.println(freelife.getUsername());
    }
}

데이터 변경이 일어 나지 않는 테스트

여러번 적용해서 마지막에 데이터가 1차캐시의 객체 값과 같다면 데이터를 변경하지 않는다

Account freelife = session.load(Account.class, account.getId());
freelife.setUsername("ironman");
freelife.setUsername("superman");
freelife.setUsername("freelife");
System.out.println("=====================");
System.out.println(freelife.getUsername());

Parent 와 Child 예시

Cascade를 적용하지 않은 테스트

Parent인 Post 클래스 생성

@Entity
public class Post {

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

    @OneToMany(mappedBy = "post")
    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;
}

JpaRunner에 post 데이터 저장 테스트

Cascade를 적용하지 않으면 post만 저장되고 comment에 전파되지 않는다

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Post post = new Post();
        post.setTitle("Spring Data JPA 언제 보나...");

        Comment comment = new Comment();
        comment.setComment("빨리 보고 싶엉.");
        post.addComment(comment);

        Comment comment1 = new Comment();
        comment1.setComment("곧 보여드릴께요.");
        post.addComment(comment1);

        Session session = entityManager.unwrap(Session.class);
        session.save(post);
    }
}

Cascade를 적용한 테스트

Postcascade = CascadeType.PERSIST 옵션 적용

  • Post를 저장할 때 저장하는 Persistentcomments에 전파
  • post 라는 인스턴스가 Transient에서 Persistent 상태로 넘어갈때
  • Child에 연관관계에 있어 참조하고 있던 객체들도 같이 Persistent 상태가 되면서 같이 저장이 됨
@OneToMany(mappedBy = "post", cascade = CascadeType.PERSIST)
private Set<Comment> comments = new HashSet<>();
public void addComment(Comment comment) {
    this.getComments().add(comment);
    comment.setPost(this);
}

Cascade Persistent와 Removed를 적용한 테스트

Post에 cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) 옵션 적용

@OneToMany(mappedBy = "post", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private Set<Comment> comments = new HashSet<>();
public void addComment(Comment comment) {
    this.getComments().add(comment);
    comment.setPost(this);
}

Post 삭제 테스트

post1에 해당되는 것을 가져와 삭제해달라고 하면
delete를 호출하는 순간 Removed 상태가 되고 Removed 상태가 전파되어 comment들도 같이 Removed 상태가 된다음
TransactionCommit이 일어날때 데이터가 모두 Remove 됨

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager entityManager;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        Session session = entityManager.unwrap(Session.class);
        Post post = session.get(Post.class, 1l);
        session.delete(post);
    }
}

일반적인 Cascade 옵션 설정

보통은 아래와 같이 cascade = CascadeType.ALL 로 주어서 다 전파하면 됨

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


728x90

댓글

💲 추천 글