springframework/Spring Data JPA

Cascade, Entity 상태

Jungsoomin :) 2020. 11. 13. 21:19

Cascade = Entity의 상태변화를 전파하는 옵션

  • Entity 에서 참조하는 다른 Entity 에 Entity 상태의 변화를 같이 전이시키는 것
  • Account 엔티티 의 변화가 A -> B 로 갈 때 참조하는 Study 의 변화도 A -> B 로 변화하도록 전이시키는 것
  • 기본 값은 "없음" 이다.

Entity 의 상태

  • Transient : JPA 가 모르는 상태
  • Persistent : Session.save() ,  해당 Entity 를 영속화 하여 JPA 가 알고 있는 상태, 관리 중이며 데이터베이스에 insert 할 때에 일어난다. ( 1차 캐시 : PersistenceContext에 해당 Entity가 관리되는 경우 Entity 정보를 캐싱, 캐싱정보 사용 시 데이터베이스에 접근하지 않음 / Dirty Checking : JPA 에서 관리하는 Persistent 상태의 Entity 의 변경사항을 모니터링한다. /  Write Behind : Entity 상태의 변화 값을 최대한 늦게 적용한다. [즉, Dirty Checking + Write Behind 를 사용하여 Entity 변화 값을 감지, 변경 값이 있을시 Update 문을 자동으로 실행 할 수 있게 되는 것] )
  • Detached : JPA가 관리하지 않는 상태, 트랜잭션이 끝나 Entity 가 밖에서 사용 될 때를 의미 함. 다시 ReAttach 하기 위해서는 Session.update() , merge() , saveOrUpdate() 를 사용한다.
  • Removed : JPA가 관리하고 있으나, 삭제하기로 한 상태이다. Session.delete() 사용, JPA가 관리 중이나 실제 commit 시 삭제가 일어난다.

@Component
@Transactional
public class JpaRunner implements ApplicationRunner {

    @PersistenceContext
    private EntityManager entityManager;
    @Override
    public void run(ApplicationArguments args) throws Exception {
        //Transient 상태
        Account account = new Account();
        account.setUsername("Jungsoomin2");
        account.setPassword("jpa");

        Study study = new Study();

        study.setName("spring data jpa");

        account.addStudy(study);


        Session session = entityManager.unwrap(Session.class);

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

        // PersistenceContext 에 저장된 Entity 를 1차 캐싱하고 있기 때문에, 데이터베이스에서 Select 하지 않는다.!
        Account loadAccount = session.load(Account.class, account.getId());

        // Dirty Checking
        loadAccount.setUsername("soominJung");
        loadAccount.setUsername("soominJung2");
        //Write Behind / PersistenceContext 저장하여 모니터링한 결과 값이 기존 Entity 정보와 동일하기 때문에 Update 문이 일어나지 않는다!.
        loadAccount.setUsername("Jungsoomin2");
        System.out.println("==========================================");
        System.out.println(loadAccount.getUsername());
        System.out.println("==========================================");

//        return loadAccount;  << Detached 상태
    }
}

 

Persistent 상태, 1차 캐시, DirtyChecking + Write Behind 로 인해 Select 문 없이 Entity 정보가 출력 된 후 Insert 트랜잭션이 진행

==========================================
soominJung
==========================================
Hibernate: 
    insert 
    into
        account
        (city, state, home_street, zip_code, created, password, username, yes, id) 
    values
        (?, ?, ?, ?, ?, ?, ?, ?, ?)
Hibernate: 
    insert 
    into
        study
        (name, owner_id, id) 
    values
        (?, ?, ?)
Hibernate: 
    update
        account 
    set
        city=?,
        state=?,
        home_street=?,
        zip_code=?,
        created=?,
        password=?,
        username=?,
        yes=? 
    where
        id=?

Non-owning Entity 이자 부모테이블 인 Post. / Cascade 속성을 지정하지 않은 상태

@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){
        comments.add(comment);
        comment.setPost(this);
    }


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Set<Comment> getComments() {
        return comments;
    }

    public void setComments(Set<Comment> comments) {
        this.comments = comments;
    }
}

Owner Entity 이자 자식 테이블인 Comment

@Entity
public class Comment {

    @Id @GeneratedValue
    private Long id;

    private String comment;

    @ManyToOne
    private Post post;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public Post getPost() {
        return post;
    }

    public void setPost(Post post) {
        this.post = post;
    }
}

두 테이블은 부모 자식 관계트랜젝션이 전파되어야 하는 관계에 있다.

 


Non-owning  Entity 에서 맵핑 진행시 Owner Entity 에는 영속화 작업이 일어나지 않는다.

  • "릴레이션이 부모 자식 관계가 아니다." 라는 뜻과 같다. 즉 각 Entity 는 독립적이다.
  • 테이블이 부모 자식 관계를 가지고 있을 경우, 테이블에 변경사항이 전파되야하며, 이를 위해 cascade 속성을 사용하게 된다.
@Component
@Transactional
public class JpaParentChileRunner implements ApplicationRunner {

    @PersistenceContext
    private 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);
    }
}
Hibernate: 
    select
        nextval ('hibernate_sequence')
Hibernate: 
    insert 
    into
        post
        (title, id) 
    values
        (?, ?)
datajpa=# select * from post;
 id |           title
----+----------------------------
  8 | Spring Data JPA 언제 보나.
(1개 행)


datajpa=# select * from comment;
 id | comment | post_id
----+---------+---------
(0개 행)


datajpa=#

부모자식 테이블의 트랜젝션 전파를 위한 Cascade 속성

 

양방향 관계를 설정하면서 cascade 속성을 주어 Persistent , remove 상태를 전파시키도록 선언.

@Entity
public class Post {

    @Id @GeneratedValue
    private Long id;

    private String title;

    // Entity 를 관리할 때 참조하는 Comment Entity 도 Persistent 상태로 관리해주렴.
    @OneToMany(mappedBy = "post", cascade = {CascadeType.PERSIST,CascadeType.REMOVE})
    private Set<Comment> comments = new HashSet<>();

    public void addComment(Comment comment){
        comments.add(comment);
        comment.setPost(this);
    }


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Set<Comment> getComments() {
        return comments;
    }

    public void setComments(Set<Comment> comments) {
        this.comments = comments;
    }
}

자식 테이블로 영속화될 Comment = cascade 속성으로 인해 Post Entity 에 상태변화가 일어날 경우 멤버변수로 참조되고 있는 Comment Entity 도 상태가 전파 된다.

@Entity
public class Comment {

    @Id @GeneratedValue
    private Long id;

    private String comment;

    @ManyToOne
    private Post post;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public Post getPost() {
        return post;
    }

    public void setPost(Post post) {
        this.post = post;
    }
}

DDL = 부모 테이블과 자식테이블에 insert 문이 모두 실행되는 상태.

Hibernate: 
    select
        nextval ('hibernate_sequence')
Hibernate: 
    select
        nextval ('hibernate_sequence')
Hibernate: 
    select
        nextval ('hibernate_sequence')
Hibernate: 
    insert 
    into
        post
        (title, id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        comment
        (comment, post_id, id) 
    values
        (?, ?, ?)
Hibernate: 
    insert 
    into
        comment
        (comment, post_id, id) 
    values
        (?, ?, ?)

테이블 결과

datajpa=# select * from comment;
 id |      comment       | post_id
----+--------------------+---------
  2 | 곧 보여드릴게요.   |       1
  3 | 빨리 보고 싶어요.. |       1
(2개 행)


datajpa=# select * from post;
 id |           title
----+----------------------------
  1 | Spring Data JPA 언제 보나.
(1개 행)


datajpa=#

CascadeType.REMOVE 전파 결과

@Component
@Transactional
public class JpaParentChileRunner implements ApplicationRunner {

    @PersistenceContext
    private 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);

    }
}

DDL = 부모테이블과 자식테이블에 모두 delete 문이 실행되고 있음.

Hibernate: 
    delete 
    from
        comment 
    where
        id=?
Hibernate: 
    delete 
    from
        comment 
    where
        id=?
Hibernate: 
    delete 
    from
        post 
    where
        id=?

테이블 결과 = 부모자식 테이블 관계에 맞게 트랜잭션이 전파됨.

datajpa=# select * from comment;
 id | comment | post_id
----+---------+---------
(0개 행)


datajpa=# select * from post;
 id | title
----+-------
(0개 행)


datajpa=#

부모 - 자식 릴레이션 관계 설정 시 CascadeType.ALL 을 지정해서 전부 전파시키는 것이 일반적이다.

@Entity
public class Post {

    @Id @GeneratedValue
    private Long id;

    private String title;

    // Entity 를 관리할 때 참조하는 Comment Entity 도 Persistent 상태로 관리해주렴.
    @OneToMany(mappedBy = "post", cascade = {CascadeType.ALL})
    private Set<Comment> comments = new HashSet<>();

    public void addComment(Comment comment){
        comments.add(comment);
        comment.setPost(this);
    }


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Set<Comment> getComments() {
        return comments;
    }

    public void setComments(Set<Comment> comments) {
        this.comments = comments;
    }
}

 

'springframework > Spring Data JPA' 카테고리의 다른 글

Query  (0) 2020.11.13
Fetch 전략  (0) 2020.11.13
관계 맵핑, 1 : N  (0) 2020.11.13
Value 타입 맵핑  (0) 2020.11.13
엔티티타입 맵핑  (0) 2020.11.13