아카이브

[스프링 데이터 JPA] 스프링 데이터 JPA 2. Entity.save() 본문

Spring/스프링 데이터 JPA

[스프링 데이터 JPA] 스프링 데이터 JPA 2. Entity.save()

주멘이 2021. 1. 16. 22:46

JpaRepository의 save()는 단순히 새 엔티티를 추가하는 메서드가 아닙니다

  • Transient(새로운) 상태의 객체라면 EntityManager.persist()
  • Detached(이미 있는) 상태의 객체라면 EntityManager.merge()  

Transient인지 Detached 인지 어떻게 판단하는가?

  • 엔티티의 @Id 프로퍼티가 null이면 Transient 상태로 판단하고 id가 null이 아니면 Detached 상태로 판단한다.
    • @Id == null? Transient : Detached
  • 엔티티가 Persistable 인터페이스를 구현하고 있다면 isNew() 메서드에 위임한다
  • JpaRepositoryFactory를 상속받는 클래스를 만들고 getEntityInfomration()을 오버 라이딩해서 자신이 원하는 판단 로직을 구현할 수도 있습니다  

EntityManager.persist()

 

EntityManager (Java EE 6 )

 void refresh(java.lang.Object entity, LockModeType lockMode, java.util.Map  properties)           Refresh the state of the instance from the database, overwriting changes made to the entity, if any, and lock it with respect to given lock mod

docs.oracle.com

Entity 상태

 

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

Cascade 엔티티의 상태변화를 전파시키는 옵션 Transient: JPA가 모르는 상태 데이터베이스에 들어갈지 안 들어갈지도 전혀 모르는 상태 new Object() save()를 호출하기 전 Persistent: JPA가 관리중인 상태 1

knoc-story.tistory.com

  • Transient : 새로 만들어진 객체
    • ID가 없고
    • ID 맵핑되는 데이터베이스 레코드가 전혀 없는 상태
    • Hibernate, Database 둘 다 아무도 모름
  • Persistent: persistenceContext 가 관리를 하는 상태
    • Transient 상태의 객체를. persist() 했을 때
    • 상태변화를 감지하거나 필요할 때마다 데이터베이스 sync를 하거나

EntityManager.merge()

 

EntityManager (Java EE 6 )

 void refresh(java.lang.Object entity, LockModeType lockMode, java.util.Map  properties)           Refresh the state of the instance from the database, overwriting changes made to the entity, if any, and lock it with respect to given lock mod

docs.oracle.com

  • Detached : 한 번이라도 데이터베이스에 Persistent 상태가 됐던 객체
    • 이 객체에 맵핑이 되는 레코드가 테이블에 있는 경우
    • 이 객체는 ID가 있음
    • ID에 맵핑이 되는 테이블 데이터가 있을 수도 있고 없으면 새로 추가

 

테스트 코드

    @PersistenceContext
    EntityManager entityManager;

    /**
     * save()
     * 새로운 객체라면 persist()를 호출한다.
     * 새로운 객체가 아닐 경우는 merge()를 호출한.
     */
    @Test
    public void saveAndUpdateTest() {
        /**
         * @Id가 null이면 Transient 상태로 판단
         * */
        Post post = new Post();
        post.setTitle("JPA");
        Post save = postRepository.save(post);// id가 없는 transient 상태이므로 persist -> insert

        // post가 PersistenceContext에서 관리(캐싱)되는 상태이기 때문
        assertThat(entityManager.contains(post)).isTrue();
        // entityManager가 save 인스턴스도 갖고있기 때문
        assertThat(entityManager.contains(save)).isTrue();
        assertThat(post == save);	// 같음


        /**
         * @Id가 not null이면 Detached 상태로 판단
         * */
        Post postUpdate = new Post();
        postUpdate.setId(post.getId());
        postUpdate.setTitle("HIBERNATE");
        
        // 리턴받은 updatedPost가 영속화된다.
        // merge에 넘긴 entity의 복사본을 만들고, 이를 persistent 상태로 변경하고 이 복사본을 리턴한다
        // DB에 sync를 하기에 상태변화를 DB에 update 한다 (Id가 DB에 해당하는게 없다면 INSERT)
        Post updatedPost = postRepository.save(postUpdate);// id가 있는 detached 상태이므로 merge -> update

//      postUpdate.setTitle("isTransient"); // postUpdate는 Detached 상태라 PersistentContext가 관리대상이 아니라서, 실제 save가 발생하는 시점(Write Behind)에 반영되지 않는다.
        updatedPost.setTitle("isPersistent");   // updatedPost는 persistent 상태이기에, write behind 시점에 이 부분이 반영된다.
        assertThat(entityManager.contains(updatedPost)).isTrue();   // 영속화 O
        assertThat(entityManager.contains(postUpdate)).isFalse();    // 영속화 X
        assertThat(post == save);	// 다름

        List<Post> all = postRepository.findAll();
        assertThat(all.size()).isEqualTo(1);

        /**
         * Best practice는 parameter로 전달한 instance 대신에
         * return받은 instance를 항상 사용하는 것이다.
         * */


    }

 

save()를 호출하면

1. ID가 없는 경우에는 persist()

  • 새로운 객체이기에 Transient 상태이고, persist()에 객체를 넘긴다.
  • persist()에 넘겨준 객체 자체가 영속화
  • save()는 항상 영속화된 객체를 리턴해준다.

2. ID가 있는 경우에는 merge()

  • detached 상태이고, 전달받은 객체의 복사본을 만들어서, 이를 통해서 DB 영속화를 하고
  • save()는 항상 영속화 된 객체를 리턴해준다.

 

Managed 객체의 장점

Hibernate, JPA가 알아서 상태변화를 감지하다가 DB Sync를 하고 가져오면 최신 상태를 가져올 수 있다