📍영속성 컨텍스트와 EntityManager? 왜 공부해야하지?
영속성 컨텍스트와 EntityManager은 JPA의 핵심 개념이다.
이 둘을 모른다면 JPA가 주는 이점을 100% 누릴 수 없다.
(SQL을 사용하는 것보다 못한 상황이 일어난다)
📍영속성 컨텍스트(PersistenceContext)
Entity를 영구 저장하는 환경이라는 뜻
- 눈에 보이지 않는 논리적 개념
- 애플리케이션과 DB 사이 객체를 보관하는 가상의 DB로 생각하기
- EntityManager를 통해 Entity를 영속성 컨텍스트에 보관, 관리
📍EntityManager
EntityManager를 통해 영속성 컨텍스트에 접근하고 관리
- EntityManager를 생성하면 그 안에 영속성 컨텍스트 있음
- EntityManagerFactory통해 요청이 올 때 마다 EntityManager 생성
- EntityManager는 내부적으로 Connection 사용하여 DB 접근
spring에서 EntityManager 여러 개, 영속성 컨텍스트 1개 존재
- 엔티티 매니저:영속성 컨텍스트 = N : 1
📍엔티티의 생명주기
entity는 EntityManager를 통해 영속성 컨텍스트에 보관, 관리, 제거된다. 그에 따른 엔티티에 생명주기를 알아보자.
👀 EntityManager 생성
- EntityManagerFactory
- 설정 파일에서(ex. Persistence.xml) 작성한 psersistence-unit(DB 설정 정보)으로 생성한다.
- EntityManager
- 위에서 생성한 EntityManagerFactory에서 생성이 가능하다.
// hello는 psersistence-unit 이름
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
// 이를 통해 엔티티를 영속성 컨텍스트에 관리 가능
EntityManager em = emf.createEntityManager();
👀 엔티티의 생명주기
비영속(new)
Member member = new Member(); // 엔티티(객체) 생성
- 객체 생성 단계.
- 엔티티가 영속성 컨텍스트에 없는 상태 (= 영속성 컨텍스트와 관계 x)
영속(managed)
em.persist(member);
- 엔티티가 영속성 컨텍스트에 저장된다.
- 즉, 영속성 컨텍스트가 엔티티를 관리하는 로직.
준영속(detached)
em.detach(member);
- 엔티티가 영속성 컨텍스트에 저장되었다가 분리된 상태
- 엔티티 영속 상태에서 준영속 상태로 간 상태!
- 영속성 컨텍스트가 엔티티 관리하지 않음
- 영속성 컨텍스트가 제공하는 기능 사용하지 못함
- 준영속 상태로 만드는 법
- em.detach(entity);
- 특정 엔티티만 준영속 상태로 변환
- em.clear()
- 영속성 컨텍스트 초기화(다 지움)
- em.close()
- 영속성 컨텍스트 종료
- em.detach(entity);
삭제(removed)
em.remove(member);
- 영속성 컨텍스트에 있는 엔티티를 삭제
- 이때 DB에서도 삭제
- 주의: 트랜잭션이 끝나거나 EntityManger를 플러시 하여 삭제 쿼리가 나가야 DB에서 삭제된다.
📍뭔지는 알겠는데 왜 사용해야 할까?
영속성 컨텍스트의 장점은 다음과 같다. 하나씩 알아보자.
1. 1차 캐시
2. 동일성(identity) 보장
3. 트랜잭션을 지원하는 쓰기 지연
4. 변경 감지
5. 지연 로딩
✔️ 1차 캐시
영속성 컨텍스트에 1차 캐시가 존재한다.
- 캐시는 map 형태로 key-value 저장되어진다.
- key: DB의 PK(기본 키)
- value: 객체
- JPA의 역할은 영속성 컨텍스트에 1차 캐시를 확인하는 것이다.
- 1차 캐시에 없다면 그 때 DB로 가서 조회를 진행한다.
- DB에 데이터 있으면 가져와서 1차 캐시에 저장 후 데이터를 반환해준다.
사실 1차 캐시는 큰 도움이 되지 않는다.
이유1. 1차 캐시가 있는 EntityManager는 트랜잭션 단위로 만들고 사라진다. 즉, 1차 캐시가 살아있는 시간은 매우 짧아 성능에 큰 효과는 없다.
이유2. 트랜잭션마다 각자 EntityManger를 사용한다. 즉, 각자 다른 영속성 컨텍스트와 1차 캐시를 가진다.
✔️ 동일성(identity) 보장
같은 트랜잭션 안에서 같은 객체는 == 비교를 보장해준다.
// 같은 PK 값 데이터 찾음
// 같은 데이터이지만 저장된 객체는 member1과 member2로 다름
Member member1 = em.find(Member.class, 1L);
Member member2 = em.find(Member.class, 1L);
// 이 때 == 비교를 하면?
System.out.println("result: "+(member1 == member2));
// 결과
result: true
JPA에서 동일성 보장은 같은 트랜잭션 안에서 동일한 엔티티 객체를 두 번 이상 조회할 때, 해당 엔티티가 동일한 객체로 처리된다는 것을 의미한다.
이렇게 함으로써 데이터의 일관성을 유지하고 불필요한 중복 객체 생성이나 데이터 변경 충돌을 방지한다!
✔️ 트랜잭션을 지원하는 쓰기 지연
"데이터베이스에 쿼리를 바로 보내지 않고, 한 트랜잭션 안에서 쿼리를 모아서 한 번에 보낸다."
feat: 트랜잭션(Transaction)은 데이터베이스에서 하나의 작업 단위를 의미하며, 데이터의 일관성과 무결성을 보장하기 위해 사용된다.
이 과정은 성능 최적화와 네트워크 부하 감소를 위해 사용되며 여기서 중요한 역할을 하는 것이 쓰기 지연 SQL 저장소다.
JPA는 필요할 때까지 DB와 통신하지 않는다. 대신 아래 두 가지 상황에서 쿼리를 실제로 DB로 보내게 된다.
- 트랜잭션 커밋 시점 (transaction.commit())
- 트랜잭션이 끝나면서 JPA가 쓰기 지연 SQL 저장소에 모아둔 모든 쿼리를 DB로 보냄.
- 예를 들어, INSERT, UPDATE, DELETE 쿼리들이 한꺼번에 실행된다.
- 플러시 시점 (em.flush())
- 개발자가 강제로 em.flush()를 호출하면, 영속성 컨텍스트에 쌓인 SQL이 DB로 보내진다.
- JPA가 필요하다고 판단하면 자동으로 flush를 호출하기도 함 (예: JPQL 실행 전).
✔️ 변경 감지(Dirty checking)
영속성 컨텍스트는 그 안에서 보관하는 데이터에 변경이 일어났는지 확인해준다.
- 데이터 변경이 일어났다면?
- JPA가 UPDATE 쿼리문 날려준다.
- 즉, 값 변경 후 update 쿼리문 날릴 코드가 필요가 따로 없다.
!작동 순서!
트랜잭션 커밋이 발생하면
- flush() 호출
- 1차 캐시에서 entity와 스냅샷 비교
- 스냅샷: 최초로 영속성 컨텍스트에 들어온 객체 상태
- entity: 실제 값
- entity와 스냅샷이 다르다면 쓰기 지연 SQL 저장소에 update 쿼리문 추가
- DB에 update 쿼리문 반영 후 commit
✔️ 지연 로딩
- 엔티티를 DB에서 가져올 때 만약 연관 관계를 가진 다른 객체를 가지고 있다면?
- 즉시 로딩: 엔티티 조회할 때 연관된 객체도 함께 DB에서 조회
- 지연 로딩: 연관된 객체는 조회x, 나중에 필요하면 그 때 DB에서 가져옴
- 각 연관 관계마다 즉시 로딩, 지연 로딩 설정이 가능하다.
📍그래서 SpringBoot 안에서는 어떻게 쓰는건데?
import org.springframework.data.jpa.repository.JpaRepository;
public interface MemberRepository extends JpaRepository<Member, Long> {
// 필요한 경우 커스텀 쿼리 메서드 추가 가능
Member findByName(String name);
}
스프링 부트를 사용하는 경우, JPA의 엔티티 매니저는 보통 위 코드처럼 Repository Layer에서 사용한다.
스프링 데이터 JPA가 내부적으로 EntityManager를 사용해 리포지토리를 구현하므로 개발자는 복잡한 엔티티 매니저 작업을 신경 쓸 필요가 없다!
하지만 어떻게 굴러가는지 정확히 알고 쓰는 게 중요하겠지? ㅎㅎ
'springboot' 카테고리의 다른 글
[SpringBoot] @Transactional을 정확하게 알아보자 (1) | 2025.01.15 |
---|---|
[SpringBoot] N+1 문제와 해결 방법 (0) | 2025.01.10 |
[SpringBoot] 끄적끄적 프로젝트 쿼리 성능 개선을 해보자 (0) | 2025.01.06 |
[SpringBoot] QueryDSL 그게 뭔데 다들 쓰는거지? (0) | 2024.12.28 |
[SpringBoot] 웹소켓으로 채팅하고 채팅방 관리하기 (0) | 2024.11.23 |