Spring 입문주차/2주차

10. Entity의 상태

note994 2024. 8. 27. 21:06

비영속(Transient)

Memo memo = new Memo(); // 비영속 상태
memo.setId(1L);
memo.setUsername("Robbie");
memo.setContents("비영속과 영속 상태");

쉽게 말하자면 new 연산자를 통해 인스턴스화 된 Entity 객체를 의미한다.

아직 영속성 컨텍스트에 저장되지 않았기 때문에 JPA의 관리를 받지 않는다.


영속(Managed)

em.persist(memo);

persist(entity) : 비영속 Entity를 EntityManager를 통해 영속성 컨텍스트에 저장하여 관리되고 있는 상태로 만든다.

 

@Test
@DisplayName("비영속과 영속 상태")
void test1() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {

        Memo memo = new Memo(); // 비영속 상태
        memo.setId(1L);
        memo.setUsername("Robbie");
        memo.setContents("비영속과 영속 상태");

        em.persist(memo);

        et.commit();

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

    emf.close();
}

비영속 상태이기 때문에 entitiesByKey=null 이다.

비영속 상태는 JPA가 관리하지 못하기 때문에 해당 객체의 데이터를 변경해도 변경 감지가 이루어지지 않는다.

em.persist(memo); 메서드 호출 후 영속성 컨텍스트에 저장되었고 MANAGED 상태 즉, JPA가 관리하는 영속 상태의 Entity가 되었다.


준영속(Detached)

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

 

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


detach(entity)

em.detach(memo);

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

영속성 컨텍스트에서 관리되다(Managed)가 분리된 상태(Detached)로 전환된다.

@Test
@DisplayName("준영속 상태 : detach()")
void test2() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {

        Memo memo = em.find(Memo.class, 1);
        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();
}

em.find(Memo.class, 1); 메서드 호출 후 MANAGED 상태이다.

 

em.detach(memo); 메서드를 호출하여 특정 Entity 객체 Memo#1를 영속성 컨텍스트에서 제거하였다.

준영속 상태로 전환되면 1차 캐시 즉, 캐시 저장소에서 제거되기 때문에 JPA의 관리를 받지 못해 영속성 컨텍스트의 어떠한 기능도 사용할 수 없다.

따라서 memo Entity 객체의 데이터를 수정해도 변경감지 기능을 사용할 수 없어 Update SQL이 수행되지 않았다.

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


clear()

em.clear();

 

clear() : 영속성 컨텍스트를 완전히 초기화한다.

영속성 컨텍스트의 모든 Entity를 준영속 상태로 전환한다.

영속성 컨텍스트 틀은 유지하지만 내용은 비워 새로 만든 것과 같은 상태가 된다.

따라서 계속해서 영속성 컨텍스트를 이용할 수 있다.

@Test
@DisplayName("준영속 상태 : clear()")
void test3() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {

        Memo memo1 = em.find(Memo.class, 1);
        Memo memo2 = em.find(Memo.class, 2);

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

        System.out.println("clear() 호출");
        em.clear();
        System.out.println("em.contains(memo1) = " + em.contains(memo1));
        System.out.println("em.contains(memo2) = " + em.contains(memo2));

        System.out.println("memo#1 Entity 다시 조회");
        Memo memo = em.find(Memo.class, 1);
        System.out.println("em.contains(memo) = " + em.contains(memo));
        System.out.println("\n 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();
}

 

em.clear(); 메서드 호출 후 완전히 비워진 영속성 컨텍스트를 확인할 수 있다.

 

다시 memo#1 Entity를 조회하여 영속성 컨텍스트에 저장된 것을 확인할 수 있다.

em.clear(); 메서드 호출 후 em.contains(memo1,2); 확인했을 때 false가 출력된 것을 확인할 수 있다.

다시 memo#1 Entity를 조회한 후 em.contains(memo); 확인했을 때 true가 출력된 것을 확인할 수 있다.

또한 memo Entity 객체의 데이터를 수정하자 트랜잭션 commit 후 Update SQL이 수행된 것을 확인할 수 있다.


close()

em.close();

close() : 영속성 컨텍스트를 종료한다.

해당 영속성 컨텍스트가 관리하던 영속성 상태의 Entity들은 모두 준영속 상태로 변경된다.

영속성 컨텍스트가 종료되었기 때문에 계속해서 영속성 컨텍스트를 사용할 수 없다.

@Test
@DisplayName("준영속 상태 : close()")
void test4() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {

        Memo memo1 = em.find(Memo.class, 1);
        Memo memo2 = em.find(Memo.class, 2);

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

        System.out.println("close() 호출");
        em.close();
        Memo memo = em.find(Memo.class, 2); // Session/EntityManager is closed 메시지와 함께 오류 발생
        System.out.println("memo.getId() = " + memo.getId());

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

    emf.close();
}

em.close(); 메서드 호출 이후 EntityManager를 사용하려고 하자 오류가 발생했다.

영속성 컨텍스트가 종료되면 계속해서 영속성 컨텍스트를 사용할 수 없다는 것을 확인할 수 있다.


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

merge(entity)

em.merge(memo);

merge(entity) : 전달받은 Entity를 사용하여 새로운 영속 상태의 Entity를 반환한다.

merge(entity) 동작

1. 해당 Entity가 영속성 컨텍스트에 없다면?

a. DB에서 새롭게 조회한다.

b. 조회한 Entity를 영속성 컨텍스트에 저장한다.

c. 전달 받은 Entity의 값을 사용하여 병합한다.

d. Update SQL이 수행된다.(수정)




2. 만약 DB에서도 없다면?

a. 새롭게 생성한 Entity를 영속성 컨텍스트에 저장한다.

b. Insert SQL이 수행된다(저장)

따라서 merge(entity) 메서드는 비영속, 준영속 모두 파라미터로 받을 수 있으며 상황에 따라 '저장'을 할 수도 '수정'을 할 수도 있다.


merge(entity) 저장

@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();
}

em.merge(memo); 호출 후 영속성 컨텍스트에 Memo#3 객체가 저장되고 Insert SQL이 추가된 것을 확인할 수 있다.

 

em.merge(memo); 호출 후 영속성 컨텍스트에 해당 값이 없어 DB에 조회 했는데도 해당 값이 없기 때문에 새롭게 생성하여 영속성 컨텍스트에 저장하고 Insert SQL이 수행되었다.

비영속 상태의 memo는 merge() 호출 후에 해당 memo 객체가 영속성 컨텍스트에 저장된게 아니라 새롭게 생성되어 영속성 컨텍스트에 저장되었기 때문에 false가 반환되었다.

새롭게 저장된 영속 상태의 객체를 반환받은 mergedMemo는 true가 반환되었다.


merge(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 객체는 준영속 상태이기 때문에 현재 영속성 컨텍스트에는 해당 객체가 존재하지 않는다.

따라서 DB에서 식별자 값을 사용하여 조회한 후 영속성 컨텍스트에 저장하고 파라미터로 받아온 준영속 상태의 memo 객체의 값을 새롭게 저장한 영속 상태의 객체에 병합하고 반환한다.

그 결과 반환된 mergedMemo의 contents를 출력하였을 때 변경되었던 내용인 "merge() 수정"이 출력되었다.

트랜잭션 commit 후 Update SQL이 수행된다.

준영속 상태의 Entity memo는 merge() 호출 후에도 영속성 컨텍스트에 저장되어 있지 않기 때문에 false가 반환되었고

새롭게 저장된 영속 상태의 객체를 반환받은 mergedMemo는 true가 반환되었다.


삭제(Removed)

em.remove(memo);

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