이번 포스팅에서는 영속성 컨텍스트에 대해 기록한다.
💡 영속성 컨텍스트란?
- JPA가 DB와 직접 연결하지 않고 엔티티 객체를 관리하는 메모리 공간이다.
- 즉, JPA를 통해 엔티티를 저장하거나 조회하면 이 객체는 영속성 컨텍스트에 보관된다.
- DB랑 직접 연결해서 쓰면 매번 SQL을 실행해야 하지만, JPA는 영속성 컨텍스트라는 임시 저장소를 이용해서 객체를 메모리에 올려두고 관리할 수 있다.
JPA는 엔티티를 직접 DB에 저장하지 않고, 영속성 컨텍스트에 먼저 보관한다.
필요하면 영속성 컨텍스트에서 꺼내 쓰고, 수정된 내용은 자동으로 DB에 반영된다.
💡 영속성 컨텍스트의 주요 특징
1️⃣ 1차 캐시
- 영속성 컨텍스트는 엔티티와 식별자를 Map 형태로 저장하는 1차 캐시를 내장하고 있다.
- 동일 트랜잭션 내에서 같은 엔티티를 조회할 때, DB 쿼리 없이 1차 캐시에서 바로 데이터를 반환하여 성능을 향상시킨다.
- 엔티티가 1차 캐시에 저장되어 있기 때문에, 같은 식별자(@Id)에 대한 엔티티는 매번 같은 인스턴스에 접근할 수 있다.
2️⃣ 쓰기 지연
- 엔티티에 대한
persist()와 같은 작업은 즉시 DB에 반영되지 않고, 쓰기 지연 저장소에 쿼리를 모아둔다. 이 쿼리들은 트랜잭션이 커밋될 때 한 번에 DB로 전송되어 불필요한 쿼리 실행을 줄여준다.
3️⃣ 변경 감지(Dirty Checking)
- 영속성 컨텍스트는 엔티티의 스냅샷을 저장해 둔다.
- 트랜잭션 커밋 시점에 현재 엔티티와 스냅샷을 비교하여 변경된 부분을 감지하고, 해당 부분에 대한 UPDATE 쿼리를 자동으로 생성하여 쓰기 지연 저장소에 추가한다.
4️⃣ 지연 로딩(Lazy Loading)
- JPA에서는 데이터를 조회할 때, 즉시 로딩(EAGER)과 지연 로딩(LAZY) 두 가지 방식이 있다.
- 즉시 로딩(EAGER) 방식은 데이터를 조회할 때 연관된 데이터까지 한 번에 불러오는 것이다.
- 지연 로딩(LAZY) 방식은 필요한 시점에 연관된 데이터를 불러오는 것이라고 할 수 있다.
💡 EntityManager란?
- EntityManager는 JPA에서 데이터베이스와 소통하는 핵심 객체이다.
- 영속성 컨텍스트를 관리하고, 엔티티를 저장, 조회, 수정, 삭제하는 역할을 한다.
- 쉽게 말하면 EntityManager는 JPA의 비서같은 존재이다.
1️⃣ persist()를 사용하여 엔티티 저장(INSERT)
- persist()를 호출하면 DB에 즉시 저장되지 않고, 영속성 컨텍스트에 먼저 저장된다.
- 트랜잭션이 끝나면 JPA가 자동으로 DB에 INSERT SQL을 실행한다.
Member member = new Member();
member.setName("JPA");
em.persist(member);
2️⃣ find()를 사용하여 엔티티 조회(SELECT)
- 먼저 영속성 컨텍스트에서 찾고, 없으면 그제서야 DB에서 가져온다.
- 같은 id의 엔티티를 여러 번 조회 시, DB를 다시 조회하지 않고 같은 객체를 반환한다.
@Transactional
public void findUser() {
User user = entityManager.find(User.class, 1L);
System.out.println(user.getName());
}
3️⃣ 엔티티 값을 변경하면 자동 반영(UPDATE)
- JPA는 변경 감지(Dirty Checking) 기능이 있어서 setName() 등으로 값을 바꾸면 자동으로 UPDATE SQL을 실행한다.
- persist() 없이 단순히 엔티티 값만 변경해도 자동 저장된다. (트랜잭션 종료 시 flush())
@Transactional
public void updateUser() {
User user = entityManager.find(User.class, 1L);
user.setName("Java");
}
4️⃣ remove()를 사용하여 엔티티 삭제(DELETE)
- remove()를 호출하면 영속성 컨텍스트에서 제거된다.
- 트랜잭션이 끝나면 자동으로 DELETE SQL이 실행된다.
@Transactional
public void deleteUser() {
User user = entityManager.find(User.class, 1L);
entityManger.remove(user);
}
5️⃣ detach()를 사용하여 영속성 컨텍스트에서 분리
- detach()를 호출하면 JPA가 이 객체를 관리하지 않는다.
- 이후 변경해도 DB에 반영되지 않는다. (변경 감지 x)
@Transactional
public void detachUser() {
User user = entityManager.find(User.class, 1L);
entityManger.datach(user);
user.setName("Postman");
}