[JPA] Entity의 4가지 상태(영속, 비영속, 준영속, 삭제)

Sunghun Kim·2024년 11월 5일

Jpa

목록 보기
2/10

1. 비영속(Transient)

  • 비영속(Transient)
    Memo memo = new Memo(); // 비영속 상태
    memo.setId(1L);
    memo.setUsername("Robbie");
    memo.setContents("비영속과 영속 상태");
    • 쉽게 말하자면 new 연산자를 통해 인스턴스화 된 Entity 객체를 의미.
    • 아직 영속성 컨텍스트에 저장되지 않았기 때문에 JPA의 관리를 받지 않습니다.
    • 순수한 Entity 클래스를 new 연산자로 하나 만든 상태.
    • 영속성 컨텍스트 JPA가 관리하고 있는 Entity가 아닌 순수한 Entity 상태 ⇒ 비영속 상태
    • 비영속 상태는 JPA가 관리하지 못하기 때문에 해당 객체의 데이터를 변경(set 메서드)해도 변경 감지가 이루어지지 않습니다.

2. 영속(Managed)

  • persist(entity) : 비영속 Entity를 EntityManager를 통해 영속성 컨텍스트에 저장하여 관리되고 있는 상태로 만듭니다.
    -persist 메서드 호출 후 영속성 컨텍스트에 저장되고 MANAGED 상태 즉, JPA가 관리하는 영속 상태의 Entity가 된다.

3. 삭제(Removed)

  • remove(entity) : 삭제하기 위해 조회해온 영속 상태의 Entity를 파라미터로 전달받아 삭제(DELETED) 상태로 전환합니다.

4. 준영속(Detached)

  • 준영속 상태는 영속성 컨텍스트에 저장되어 관리되다가 분리된 상태를 의미합니다.

영속 상태에서 준영속 상태로 바꾸는 방법

4-1. Detach

detach(entity) : 특정 Entity만 준영속 상태로 전환합니다.

  • 준영속 상태(Detached)는 영속성 컨텍스트에 저장되어 관리되다가 분리된 상태를 의미합니다.
@Test
    @DisplayName("준영속 상태 : detach()")
    void test2() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {

            Memo memo = em.find(Memo.class, 1); // 여기는 MANAGED(영속)상태
            System.out.println("memo.getId() = " + memo.getId());
            System.out.println("memo.getUsername() = " + memo.getUsername());
            System.out.println("memo.getContents() = " + memo.getContents());

            // em.contains(entity) : Entity 객체가 현재 영속성 컨텍스트에 저장되어 관리되는 상태인지 확인하는 메서드
            System.out.println("em.contains(memo) = " + em.contains(memo));

            System.out.println("detach() 호출");
            em.detach(memo); // 준영속 상태
            System.out.println("em.contains(memo) = " + em.contains(memo));

            System.out.println("memo Entity 객체 수정 시도");
            memo.setUsername("Update");
            memo.setContents("memo Entity Update");

            System.out.println("트랜잭션 commit 전");
            et.commit();
            System.out.println("트랜잭션 commit 후");

        } catch (Exception ex) {
            ex.printStackTrace();
            et.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }

console 결과

Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
memo.getId() = 1
memo.getUsername() = Robbert
memo.getContents() = @Transactional 전파 테스트 중! 2
em.contains(memo) = true
detach() 호출
em.contains(memo) = false
memo Entity 객체 수정 시도
트랜잭션 commit 전
트랜잭션 commit 후
  • 속성 컨텍스트에서 제거되면서 준영속 상태로 전환되면 1차 캐시 즉, 캐시 저장소에서 제거되기 때문에 JPA의 관리를 받지 못해 영속성 컨텍스트의 어떠한 기능도 사용할 수 없습니다.

  • 따라서 detached상태 일때 memo Entity 객체의 데이터를 수정해도 변경감지(Dirty Checking) 기능을 사용할 수 없어 Update SQL이 수행되지 않았습니다.

  • em.contains(memo); : 해당 객체가 영속성 컨텍스트에 저장되어 관리되는 상태인지 확인하는 메서드로 em.detach(memo); 이후 확인했을 때 false가 출력된 것을 확인할 수 있습니다.

4-2. Clear

  • clear() : 영속성 컨텍스트를 완전히 초기화합니다.
    • 영속성 컨텍스트의 모든 Entity를 준영속(Detached) 상태로 전환합니다.
    • 영속성 컨텍스트 틀은 유지하지만 내용은 비워 새로 만든 것과 같은 상태가 됩니다.
    • 따라서 계속해서 영속성 컨텍스트를 이용할 수 있습니다.

4-3. Close

  • close() : 영속성 컨텍스트를 종료합니다.
    • 해당 영속성 컨텍스트가 관리하던 영속성 상태의 Entity들은 모두 준영속(Detached) 상태로 변경됩니다.
    • 영속성 컨텍스트가 종료되었기 때문에 계속해서 영속성 컨텍스트를 사용할 수 없습니다.
    • em.close(); 메서드 호출 이후 EntityManager를 사용하려고 하면 오류가 발생합니다.
    • 영속성 컨텍스트가 종료되면 계속해서 영속성 컨텍스트를 사용할 수 없다는 것을 확인할 수 있습니다.

close후 영속성 컨텍스트를 사용했을 시 에러

java.lang.IllegalStateException: Session/EntityManager is closed
	at org.hibernate.internal.AbstractSharedSessionContract.checkOpen(AbstractSharedSessionContract.java:409)
	at org.hibernate.engine.spi.SharedSessionContractImplementor.checkOpen(SharedSessionContractImplementor.java:164)
	at org.hibernate.internal.SessionImpl.find(SessionImpl.java:2313)
	at org.hibernate.internal.SessionImpl.find(SessionImpl.java:2298)
	at EntityStateTest.test4(EntityStateTest.java:148)

준영속 상태에서 다시 영속 상태로 바꾸는 방법

4-4. merge

  • merge(entity) : 전달받은 Entity를 사용하여 새로운 영속 상태의 Entity를 반환합니다.
  • merge(entity) 동작
    • 파라미터로 전달된 Entity의 식별자 값으로 영속성 컨텍스트를 조회합니다.
      1. 해당 Entity가 영속성 컨텍스트에 없다면?
        1. DB에서 새롭게 조회합니다.
        2. 조회한 Entity를 영속성 컨텍스트에 저장합니다.
        3. 전달 받은 Entity의 값을 사용하여 병합합니다.
        4. Update SQL이 수행됩니다. (수정)
      2. 만약 DB에서도 없다면 ?
        1. 새롭게 생성한 Entity를 영속성 컨텍스트에 저장합니다.
        2. Insert SQL이 수행됩니다. (저장)
  • 따라서 merge(entity) 메서드는 비영속, 준영속 모두 파라미터로 받을 수 있으며 상황에 따라 ‘저장’을 할 수도 ‘수정’을 할 수도 있습니다.

DB에 없을시

@Test
    @DisplayName("merge() : 저장")
    void test5() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {

            Memo memo = new Memo();
            memo.setId(3L);
            memo.setUsername("merge()");
            memo.setContents("merge() 저장");

            System.out.println("merge() 호출");
            Memo mergedMemo = em.merge(memo);

            System.out.println("em.contains(memo) = " + em.contains(memo));
            System.out.println("em.contains(mergedMemo) = " + em.contains(mergedMemo));

            System.out.println("트랜잭션 commit 전");
            et.commit();
            System.out.println("트랜잭션 commit 후");

        } catch (Exception ex) {
            ex.printStackTrace();
            et.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }

console 결과

merge() 호출
Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
em.contains(memo) = false
em.contains(mergedMemo) = true
트랜잭션 commit 전
Hibernate: 
    /* insert com.sparta.entity.Memo
        */ insert 
    into
        memo (contents, username, id) 
    values
        (?, ?, ?)
트랜잭션 commit 후
  • em.merge(memo); 호출 후 영속성 컨텍스트에 해당 값이 없어 DB에 조회 했는데도 해당 값이 없기 때문에 새롭게 생성하여 영속성 컨텍스트에 저장하고 Insert SQL이 수행되었습니다.
    • 비영속 상태의 memo는 merge() 호출 후에 해당 memo 객체가 영속성 컨텍스트에 저장된게 아니라 새롭게 생성되어 영속성 컨텍스트에 저장되었기 때문에 false가 반환되었습니다.
    • 새롭게 저장된 영속 상태의 객체를 반환받은 mergedMemo는 true가 반환되었습니다.
    • merge하면 1차 캐시에도 없고 Db에 없으니 새롭게 만들어 persist 처럼 영속성 컨텍스트에 저장을 하니 Action Queue에 Insertions가 담긴것.

해당 Entity가 영속성 컨텍스트에 없을시

@Test
    @DisplayName("merge() : 수정")
    void test6() {
        EntityTransaction et = em.getTransaction();

        et.begin();

        try {

            Memo memo = em.find(Memo.class, 3);
            System.out.println("memo.getId() = " + memo.getId());
            System.out.println("memo.getUsername() = " + memo.getUsername());
            System.out.println("memo.getContents() = " + memo.getContents());

            System.out.println("em.contains(memo) = " + em.contains(memo));

            System.out.println("detach() 호출");
            em.detach(memo); // 준영속 상태로 전환
            System.out.println("em.contains(memo) = " + em.contains(memo));

            System.out.println("준영속 memo 값 수정");
            memo.setContents("merge() 수정");

            System.out.println("\n merge() 호출");
            Memo mergedMemo = em.merge(memo);
            System.out.println("mergedMemo.getContents() = " + mergedMemo.getContents());

            System.out.println("em.contains(memo) = " + em.contains(memo));
            System.out.println("em.contains(mergedMemo) = " + em.contains(mergedMemo));

            System.out.println("트랜잭션 commit 전");
            et.commit();
            System.out.println("트랜잭션 commit 후");

        } catch (Exception ex) {
            ex.printStackTrace();
            et.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
  • detach() 메서드 호출로 조회해온 영속 상태의 memo 객체를 준영속 상태로 전환했다.
  • 준영속 상태의 memo의 값을 수정한 후 memo 객체를 사용해서 merge() 메서드를 호출했습니다.
  • memo 객체는 준영속(Detached) 상태이기 때문에 현재 영속성 컨텍스트에는 해당 객체가 존재하지 않습니다.
    • 따라서 DB에서 식별자 값을 사용하여 조회한 후 영속성 컨텍스트에 저장하고 파라미터로 받아온 준영속(Detached) 상태의 memo 객체의 값을 새롭게 저장한 영속 상태의 객체에 병합하고 반환합니다.
    • 그 결과 반환된 mergedMemo의 contents를 출력하였을 때 변경되었던 내용인 “merge() 수정”이 출력되었습니다.
    • 트랜잭션 commit 후 Update SQL이 수행됩니다.
  • 준영속 상태의 Entity memo는 merge() 호출 후에도 영속성 컨텍스트에 저장되어 있지 않기 때문에 false가 반환되었고
  • 새롭게 저장된 영속 상태의 객체를 반환받은 mergedMemo는 true가 반환되었습니다.

console 결과

Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
memo.getId() = 3
memo.getUsername() = merge()
memo.getContents() = merge() 저장
em.contains(memo) = true
detach() 호출
em.contains(memo) = false
준영속 memo 값 수정

 merge() 호출
Hibernate: 
    select
        m1_0.id,
        m1_0.contents,
        m1_0.username 
    from
        memo m1_0 
    where
        m1_0.id=?
mergedMemo.getContents() = merge() 수정
em.contains(memo) = false
em.contains(mergedMemo) = true
트랜잭션 commit 전
Hibernate: 
    /* update
        com.sparta.entity.Memo */ update memo 
    set
        contents=?,
        username=? 
    where
        id=?
트랜잭션 commit 후

영속성 4가지 상태 정리

  • 영속성 4가지 상태 ( 비영속 > 영속 > 준영속 | 삭제)

① 비영속(new/transient) - 엔티티 객체가 만들어져서 아직 저장되지 않은 상태로, 영속성컨텍스트와 전혀 관계가 없는 상태

② 영속(managed) - 엔티티가 영속성 컨텍스트에 저장되어, 영속성 컨텍스트가 관리할 수 있는 상태

③ 준영속(detached) - 엔티티가 영속성 컨텍스트에 저장되어 있다가 분리된 상태로, 영속성 컨텍스트가 더 이상 관리하지 않는 상태

④ 삭제(removed) - 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제하겠다고 표시한 상태

  • 객체의 영속성 상태는 Entity Manager 의 메소드를 통해 전환된다.

    • new -> (비영속상태) -> persist(),merge() ->
      (영속성 컨텍스트에 저장된 상태) -> flush() -> (DB에 쿼리가 전송된 상태)
      -> commit() -> (DB에 쿼리가 반영된 상태)

사진으로 보는 Entity 상태

profile
BackEnd Developer!!

0개의 댓글