1. 개요
Spring Data JPA를 제대로 활용하려면, JPA의 핵심 개념인 영속성 컨텍스트와 Entity 상태를 정확히 이해해야 한다.
이번 글에서 이를 정리하려고 한다.
2. 영속성 컨텍스트(Persistence Context)란?
JDBC, SQL Mapper와 같은 도구들은 개발자가 직접 쿼리를 작성하고, 해당 결과를 변수에 저장하는 방식으로 사용한다.
하지만, JPA는 데이터베이스와 애플리케이션 간의 데이터 동기화를 담당하는 중간 계층이 존재하는데 이것이 영속성 컨텍스트이다. 영속성 컨텍스트는 JPA의 핵심개념으로, 데이터베이스와 애플리케이션을 일관성을 유지하기 위해 Entity를 관리하는 가상의 데이터 저장소이다. 이를 통해 데이터를 효율적으로 조회하고 변경할 수 있다.
3. Entity 상태
JPA에서 엔티의 상태는 네가지로 구분된다. 간단히 정리하면, 해당 엔티티가 영속성 컨텍스트와 어떤 관계를 맺고 있는지에 따라 결정된다.
a. 비영속 상태(Transient)
- 영속성 컨텍스트에 전혀 포함되지 않는 상태의 엔티티이다.
- 데이터베이스와 아무런 연관도 없으며, 영속성 컨텍스트가 전혀 개입하지 않는 엔티티이다.
// 비영속 상태(영속성 컨텍스트와 관계가 없는 대상)
Member member = new Member(100L, "HelloJPA");
b. 영속 상태(Persistent)
- 엔티티가 영속성 컨텍스트에 포함되어 관리되는 상태이다.
- 데이터베이스와 동기화가 이루어질 수 있는 상태이다.
- 영속성 컨텍스트 내부에 1차 캐시에 저장된다.
- persist(o) 메서드나 EntityManager로부터 조회한 엔티티는 영속 상태이다.
Member member = new Member(100L, "HelloJPA");
em.persist(member); // 영속 상태
Member findMember = em.find(Member.class, 100L); // 영속 상태
c. 준영속 상태(Detached)
- 영속 상태였다가 엔티티 영속성 컨텍스트에서 더 이상 관리되지 않는 상태이다.
- 비영속 상태와 마찬가지로 데이터베이스와 아무 연관이 없다.
- detach(o), clear() 메서드를 호출하면 준영속 상태로 전환된다.
Member member = em.find(Member.class, 100L); // 영속 상태
em.detach(member); // 특정 엔티티를 준영속 상태로 전환
em.clear(); // 모든 엔티티를 준영속 상태로 전환
d. 삭제 상태(Removed)
- 엔티티가 삭제 요청을 받은 상태이다.
- 트랜잭션이 커밋되거나, flush() 메서드를 호출하면 DELETE 쿼리를 실행한다.
Member member = em.find(Member.class, 100L); // 영속 상태
em.remove(member); // 삭제 상태로 전환
4. 영속성 컨텍스트의 이점
JPA의 영속석 컨텍스트는 데이터베이스와 애플리케이션 간의 동기화를 효율적으로 관리하는 중요한 역할을 한다.
영속성 컨텍스트의 여러가지 이점에 대해서 살펴보자.
a. 영속성 컨텍스트 생명 주기
- 영속성 컨텍스트는 트랜잭션 범위 내에서 관리되며, 트래잭션이 시작될 때 생성되고 커밋 OR 롤백될 때 종료된다.
public class Jpa {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작
// 영속성 컨텍스트 범위 내에서 작업 수행
Member member = em.find(Member.class, 1L); // 데이터 조회
member.setName("Updated Name"); // 데이터 변경
tx.commit(); // 트랜잭션 커밋
em.close();
emf.close();
}
}
b. 1차 캐시(동일성 보장)
- 영속성 컨텍스트 내부에는 1차 캐시라는 저장소가 존재한다.
- 영속 상태인 엔티티는 1차 캐시에 저장된다.
- 같은 엔티티를 조회할 경우 DB에 접근하지 않고, 1차 캐시에서 해당 엔티티를 반환한다.
Member member1 = em.find(Member.class, 1L); // DB 조회 후 1차 캐시에 저장
Member member2 = em.find(Member.class, 1L); // 1차 캐시에서 반환
member1 == member2; // true => 동일성 보장
c. 쓰기 지연
- 변경된 데이터를 즉시 데이터에 반영하지 않고, 쓰기 지연 SQL 저장소에 등록한다.(Insert, Update, Delete)
- flush() OR 트랜잭션이 커밋 시 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 반영한다.
- 쿼리를 모았다가 한번에 처리하기 때문에 네트워크 I/O 작업을 줄일 수 있다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작
try {
Member member1 = new Member(101L, "A");
Member member2 = new Member(102L, "B");
em.persist(member1);
em.persist(member2);
tx.commit(); // 트랜잭션 커밋(이 시점에 DB에 반영)
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
d. 변경 감지(Dirty Checking)
- 영속 상태의 엔티티에 대해 변경된 사항을 자동으로 추적한다.
- 트랜잭션이 커밋되거나 flush() 할 때, 엔티티 상태를 확인하여 자동으로 변경된 데이터를 데이터베이스에 자동으로 반영한다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작
try {
Member findMember = em.find(Member.class, 150L);
findMember.setName("update_name");
tx.commit(); // 트랜잭션 커밋(이 시점에 자동으로 DB에 Update)
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
sql 결과
e. 지연 로딩(Lazy Loading)
- 연관된 엔티티를 실제로 사용하기 전까지 로딩을 지연시키는 전략이다.
- 연관된 엔티티가 실제로 필요할 때 데이터베이스에 조회하는 방식이다.
- 연관된 엔티티를 실제 사용시점에 조회를 하기 때문에 불필요한 쿼리를 방지할 수 있다.
@Getter
@No
@Entity
public class Member {
@Id
private Long id;
private String name;
@OneToMany(fetch = FetchType.LAZY) // 로딩 전략
private List<Order> orders;
protected Member() {
}
}
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin(); // 트랜잭션 시작
try {
// 쓰기 지연 테스트
Member member = new Member(101L, "A");
member1.getOrders(); // 이 시점에 db 조회
tx.commit();
tx.commit(); // 트랜잭션 커밋
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
'Programming > Java' 카테고리의 다른 글
[JPA] JPA에서 PK를 다루는 방법(@Id, @GeneratedValue) (0) | 2024.12.12 |
---|---|
[JPA] Entity 매핑과 DDL 자동 생성 옵션 정리 (0) | 2024.12.12 |
[Java] ProcessBuilder로 시스템 명령어 수행하기 (0) | 2024.10.08 |
[Java] 클래스 생성과 인스턴스 생성 (1) | 2024.01.05 |
[Java] 스트림(Stream) (0) | 2023.12.10 |