JPA (Java Persistence API)에서 Entity는 데이터베이스의 테이블과 매핑된 자바 객체입니다.
엔티티는 영속성 컨텍스트(Persistence Context) 내에서 관리되며, 그 상태에 따라 데이터베이스와의 상호작용 방식이 달라집니다.
JPA에서 엔티티의 상태는 다음과 같이 나눌 수 있습니다.
- 비영속(Transient)상태
- 영속(Managed) 상태
- 준영속(Detached) 상태
- 삭제(Removed) 상태
비영속(Transient) 상태란❓
Memo memo = new Memo(); // 비영속 상태
memo.setId(1L);
memo.setUsername("ZINU");
memo.setContents("비영속과 영속 상태");
비영속 상태란 "임시 상태"라고도 불리며, 영속성 컨텍스트와 무관하게 자바 애플리케이션의 메모리에서만 존재하는 상태입니다. 데이터베이스와의 연결이 없으며, 영속성 컨텍스트가 엔티티를 인식하지 않습니다.
쉽게 말하자면 new 연산자를 통해 인스턴스화된 Entity 객체를 의미합니다.
비영속(Transient) 상태 확인
@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();
}
위 코드에서 persist() 메서드로 영속성 컨텍스트에 저장하기 전 디버깅 결과를 보면 비영속 상태이기 때문에 entitiesByKey=null 인 것을 볼 수 있습니다.
entitiesByKey❓
JPA에서 엔티티를 관리하기 위해 내부적으로 사용하는 데이터 구조로, 엔티티의 식별자(키:PK)를 기준으로 엔티티를 조회하고
하는 데 사용
영속(Managed) 상태란❓
em.persist(memo);
영속 상태란 "관리 상태"라고도 불리며, 영속성 컨텍스트에 의해 관리되는 상태입니다. 이 상태의 엔티티는 JPA에 의해 트랜잭션과 영속성 컨텍스트의 생명 주기와 함께 관리됩니다.
엔티티가 영속성 컨텍스트에 저장되었으며, 변경 감지(Dirty Checking) 및 쓰기 지연 저장소(Write-Behind) 기능의 대상이 됩니다. 영속성 컨텍스트에 의해 자동으로 변경 사항이 감지되어 데이터베이스에 반영됩니다.
persist() 메서드는 비영속 Entity를 EntityManager를 통해 영속성 컨텍스트에 저장하여 관리되고 있는 상태로 만듭니다.
영속(Managed) 상태 확인
@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();
}
persist() 메서드를 통해 영속성 컨텍스트에 저장을 하면 MANAGED 상태(영속 상태)가 된 것을 볼 수 있습니다.
준영속(detached) 상태란❓
준영속 상태란 Entity가 영속성 컨텍스트에서 분리된 상태입니다. 즉, Entity가 영속성 컨텍스트에 의해 더 이상 관리되지 않으며, 메모리에서만 존재하면서 비영속 상태가 됩니다.
이 객체의 상태를 변경해도 자동으로 데이터베이스에 반영되지 않으며, 다시 영속성 컨텍스트에 병합해야 합니다.
영속 상태(Transient) ➡️ 준영속(detached) 상태
1. detach() 메서드 사용
em.detach(memo);
EntityManager의 detach() 메서드를 사용하면 특정 엔티티를 영속성 컨텍스트에서 분리하여 준영속 상태로 만들 수 있습니다.
이렇게 하면 엔티티의 상태가 더 이상 영속성 컨텍스트에 의해 관리되지 않으며, 데이터베이스와의 동기화가 이루어지지 않습니다.
@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();
}
find() 메서드를 호출하여 엔티티를 데이터베이스에서 조회하고, 영속성 컨텍스트에 반환했습니다.
따라서 find()메서드를 호출 후 MANAGE(영속 상태)가 됩니다.
detach() 메서드를 호출하여 Entity객체를 영속성 컨텍스트에서 분리했기 때문에 준영속 상태로 전환되기 때문에 Entity 객체가 사라진 것을 볼 수 있습니다.
따라서 최종 실행 결과를 보면 detach() 메서드로 영속성 컨텍스트에서 분리했기 때문에 Entity 객체를 수정해도 변경 감지 기능이 사용 안된 것을 볼 수 있습니다.
또한 contains() 메서드를 통해 해당 객체가 영속성 컨텍스트에 저장되어 관리되고 있는 상태인지 확인을 해보았지만 flase가 출력되어 관리가 되지 않은 즉 영속성 컨텍스트에 존재하지 않은 Entity객체라는 것을 알 수 있습니다.
2. clear() 메서드 사용
EntityManager의 clear() 메서드를 호출하면 영속성 컨텍스트에 관리되고 있는 모든 엔티티가 준영속 상태로 바뀝니다.
즉 영속성 컨텍스트를 완전히 초기화합니다.
이 메서드는 영속성 컨텍스트에 있는 모든 엔티티를 영속성 컨텍스트에서 제거하므로, 이후에는 그 엔티티들이 준영속 상태가 됩니다.
clear() 메서드의 가장 큰 특징은 "영속성 컨텍스트 틀은 유지하지만 내용은 비워 새로 만든 것과 같은 상태가 되는 것"입니다.
따라서 detach() 메서드와 다르게 계속해서 영속성 컨텍스트를 이용할 수 있습니다.
@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();
}
마찬가지로 find() 메서드를 호출하여 엔티티를 데이터베이스에서 조회하고, 영속성 컨텍스트에 반환했습니다.
따라서 find()메서드를 호출 후 MANAGE(영속 상태)가 됩니다.
clear() 메서드를 호출하여 영속성 컨텍스트를 틀은 유지한 채 초기화했기 때문에 Entity 객체가 사라진 것을 볼 수 있습니다.
그리고 다시 영속 상태로 만들 수 있기 때문에 위와 같이 다시 한번 find()를 사용하여 영속성 컨텍스트에 저장할 수 있습니다.
clear() 메서드 호출 후, 기존에 영속성 컨텍스트에서 관리되던 memo1과 memo2는 준영속 상태로 전환되어 contains() 메서드를 호출하여 확인을 해보면 false로 출력된 것을 볼 수 있습니다.
memo#1 엔티티를 find() 메서드로 다시 영속성 컨텍스트에 추가되어 em.contains(memo)가 true로 출력됩니다.
이후 memo 엔티티의 데이터를 수정하고 트랜잭션을 커밋하면, 수정된 내용이 데이터베이스에 반영됩니다.
3. 트랜잭션 종료
// 트랜잭션 종료 후 엔티티가 준영속 상태로 변경됨
et.commit(); // 트랜잭션 커밋
트랜잭션이 종료되면, 영속성 컨텍스트의 모든 엔티티는 기본적으로 준영속 상태로 변경됩니다.
이는 엔티티의 상태가 더 이상 자동으로 데이터베이스와 동기화되지 않음을 의미합니다.
이 경우, 엔티티를 다시 영속 상태로 만들려면 merge() 메서드를 사용해야 합니다.
merge() 메서드는 아래에서 알아보겠습니다.
4. remove() 메서드 사용
// 엔티티를 삭제 상태로 만들기
Memo memo = em.find(Memo.class, 1L); // 영속 상태
em.remove(memo); // 삭제 상태
EntityManager의 remove() 메서드를 호출하면 엔티티는 삭제 상태로 변하며, 트랜잭션이 커밋될 때 데이터베이스에서 실제로 삭제됩니다.
이 엔티티는 이후 준영속 상태로 간주되며, 삭제된 엔티티는 다시 영속성 컨텍스트에 의해 관리되지 않습니다.
5. close()
em.close();
close() 메서드는 영속성 컨텍스트를 아예 종료하는 메서드입니다.
EntityManager의 close() 메서드는 현재 EntityManager 인스턴스를 종료하고 관련된 자원을 해제합니다.
이를 통해 영속성 컨텍스트와 관련된 메모리와 데이터베이스 연결을 정리합니다. 따라서 영속성 컨텍스트가 종료되었기 때문에 계속해서 영속성 컨텍스트를 사용할 수 없습니다.
@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();
}
close() 메서드로 영속성 컨텍스트를 종료했기 때문에 다시 find() 메서드를 사용해서 영속성 컨텍스트에 추가하려면 에러가 발생합니다.
준영속(detached) 상태 ➡️ 영속 상태(Transient)
앞서 트랜잭션이 종료되었을 때 다시 영속 상태로 만들려면 merge() 메서드를 사용해야 한다고 했습니다.
merge() 메서드란❓
merge() 메서드는 이름에서 알 수 있듯이 준영속 상태의 엔티티를 다시 영속성 컨텍스트에 통합(병합)하는 데 사용됩니다. 또한 준영속 상태의 엔티티를 병합할 때 사용됩니다.
이 메서드는 엔티티의 복사본을 생성하여 영속성 컨텍스트에 저장하고, 기존의 상태를 데이터베이스에 반영합니다.
merge() 메서드 동작 방식
파라미터로 전달된 Entity의 식별자 값(PK)으로 영속성 컨텍스트를 조회합니다.
- 영속성 컨텍스트에 엔티티가 없는 경우(준영속 상태 : Detached)
- 데이터베이스에서 해당 엔티티를 찾습니다.
- 찾은 엔티티를 영속성 컨텍스트에 추가합니다.
- 전달된 엔티티의 데이터로 기존 엔티티를 업데이트합니다. → 수정
- 데이터베이스에서도 엔티티를 찾을 수 없는 경우(비영속 상태 : Transient)
- 전달된 엔티티를 새로 생성하여 영속성 컨텍스트에 추가합니다.
- 데이터베이스에 새로 저장하기 위해 INSERT SQL이 실행됩니다. → 추가(저장)
간단히 말해, merge()는 비영속 상태의 엔티티를 조회하고, 비영속, 준영속 모두 파라미터로 받을 수 있기 때문에 필요에 따라 업데이트하거나 새로 저장합니다.
@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();
}
현재 영속성 컨텍스트와 데이터 베이스에는 ID: 3(PK)가 없기 때문에 merge() 메서드를 호출했을 때 전달된 Entity를 새로 생성하고, 쓰기 지연 저장소에 Insert SQL이 추가된 것을 볼 수 있습니다.
merge() 메서드 호출 후 memo는 비영속 상태의 엔티티입니다.
이 상태에서 merge() 메서드를 호출하면, 영속성 컨텍스트에서 해당 엔티티를 찾을 수 없습니다. 데이터베이스에서도 memo의 ID에 해당하는 엔티티를 찾을 수 없기 때문에, 새로운 엔티티로 간주되어 INSERT SQL이 실행됩니다.(merge : 저장)
따라서, memo 객체는 비영속 상태로 남아 있으며, em.contains(memo)는 false를 반환합니다.
반면, merge() 메서드가 반환한 mergedMemo는 영속성 컨텍스트에 새롭게 저장된 상태이므로, em.contains(mergedMemo)는 true를 반환합니다.
Entity 삭제 : remove() 메서드란❓
em.remove(entity);
remove() 메서드를 호출하면, 해당 엔티티는 영속성 컨텍스트에서 제거되며, 이후에는 영속성 컨텍스트에서 관리되지 않습니다.
이 메서드는 엔티티를 삭제 상태로 전환하지만, 실제로 데이터베이스에서 삭제하는 것은 트랜잭션 커밋 시점에 이루어집니다.
'Framework > JPA' 카테고리의 다른 글
[JPA] JPA의 복잡함을 줄이는 Spring Data JPA (0) | 2024.07.27 |
---|---|
[JPA] 트랜잭션 관리와 영속성 컨텍스트의 관계 (@Transaction, 트랜잭션 전파) (0) | 2024.07.27 |
[JPA] 영속성 컨텍스트란 무엇일까❓ #2(1차 캐시, 변경 감지, 쓰기지연 감소) (0) | 2024.07.27 |
[JPA] 영속성 컨텍스트란 무엇일까❓ (0) | 2024.07.26 |
[JPA] Entity란 무엇일까❓(Entity: 데이터베이스와 자바 객체 간의 다리) (0) | 2024.07.26 |