JPA의 영속성 컨텍스트(Persistence Context)
- JPA가 관리하는 엔티티 객체들이 저장되는 메모리 공간(캐시)
- JPA에서 엔티티를 persist(), find(), merge() 등을 하면 영속성 컨텍스트가 이를 관리
- 즉시 DB에 반영되지 않고 영속성 컨텍스트에 저장되었다가 트랜잭션이 commit()될 때 변경 사항을 한 번에 DB에 반영 (flush() 발생) : 쓰기 지연 (Write-Behind)
- @Entity 가 붙은 클래스만 관리함!!
- @Query 나 네이티브쿼리로 가져온 임의의 결과는 관리하지 않음
엔티티 생명주기 (4가지 상태)
상태 | 설명 | 코드 예제 |
비영속 (New) | JPA가 관리하지 않는 상태 | Member member = new Member(); |
영속 (Managed) | 영속성 컨텍스트가 관리 | entityManager.persist(member); |
준영속 (Detached) | 영속성 컨텍스트에서 분리 | entityManager.detach(member); |
삭제 (Removed) | 영속성 컨텍스트에서 제거됨 | entityManager.remove(member); |
영속성 컨텍스트는 DB 조회 결과를 캐싱(1차 캐시)하여 동일한 엔티티에 대한 불필요한 쿼리 실행을 방지
// 첫 번째 조회 → DB에서 조회 후 영속성 컨텍스트에 저장
Member member1 = entityManager.find(Member.class, 1L);
// 두 번째 조회 → 영속성 컨텍스트에서 가져옴 (DB 조회 X)
Member member2 = entityManager.find(Member.class, 1L);
같은 트랜잭션 내에서 같은 ID로 조회하면 DB를 조회하지 않음
변경 감지(Dirty Checking)
- 트랜잭션이 커밋될 때 변경된 엔티티!! 를 자동으로 감지하고 DB에 반영하는 기능
- @Transactional(readOnly = false) 일때만 동작함
@Transactional
public void updateMember(Long id) {
Member member = entityManager.find(Member.class, id); // 영속 상태
member.setName("New Name"); // 변경 감지 (Dirty Checking)
} // 트랜잭션 `commit()` 시점에 `UPDATE` SQL 자동 실행
1. 엔티티 조회 (SELECT)
- find() 또는 JPQL을 통해 엔티티를 조회하면, JPA는 해당 엔티티를 영속성 컨텍스트에 저장함.
2. 엔티티 값 변경
- 영속 상태의 엔티티 값을 변경하면, setXXX()을 호출하는 즉시 DB에 반영되지 않고 영속성 컨텍스트가 변경 사항을 추적함.
- Hibernate가 원본 값(Snapshot)과 비교하여 변경된 필드를 감지함.
3. 트랜잭션 commit() 시점에 변경 사항 반영 (UPDATE)
- 트랜잭션이 끝날 때 변경된 엔티티를 자동으로 UPDATE 쿼리로 반영함.
@Transactional(readOnly = true)가 최적화되는 이유
- readOnly = true를 설정하면 Hibernate가 더티 체킹을 하지 않음.
- 즉, commit() 시점에 변경 사항을 검사하지 않으므로 불필요한 UPDATE SQL을 방지할 수 있음.
- 트랜잭션 종료 시점에 변경된 엔티티를 검사하는 오버헤드가 제거됨
- DB에도 읽기 전용 모드(SELECT 전용)로 연결됨.
save()
새로운 객체를 생성하여 저장하는 것은 더티 체킹이 아님.
JPA가 관리하는 기존 엔티티를 변경하는 것이 아니라, 새로운 엔티티를 생성하여 persist() 하는 과정이기 때문.
@Transactional(readOnly = false) 여도 JPA 네이티브 쿼리, MyBatis, JdbcTemplate, QueryDSL(JPA 아닌 버전) 등은 Hibernate의 영속성 컨텍스트가 관리하지 않음
@Transactional이 없을 때의 동작 방식
- 영속성 컨텍스트 없음 (더티 체킹 없음)
- 모든 쿼리가 DB에 즉시 적용됨
네이티브 쿼리는 JPA가 아니라, 결국 JDBC를 통해 DB가 직접 실행하는 것
네이티브 쿼리는 어떻게 실행될까?
1. EntityManager.createNativeQuery("INSERT ...") 호출
2. JPA가 SQL을 전달
3. JDBC가 SQL을 실행 (JDBC Driver가 DB와 통신)
4. DB에서 실행 후 결과 반환
JPA는 네이티브 쿼리를 실행할 수 있는 기능만 제공할 뿐, 실행 주체가 아님
네이티브 쿼리는 JPA의 영속성 컨텍스트와 관계없음
@Transactional(readOnly = false) 를 썼을 때 엔티티는 영속성 컨텍스트(서버 사이드)에 대기하면서 쓰기 지연(Write-Behind) 되는데 영속성 컨텍스트가 관리하지 않는 네이티브 쿼리는 어디서 대기?
1. 서버 사이드 : JDBC Batch Buffer (JDBC 배치가 활성화된 경우)
- JDBC 드라이버 내부의 배치 버퍼에서 대기
- commit() 시점에 한꺼번에 DB로 전송
- 예시: hibernate.jdbc.batch_size=50 설정하면 최대 50개까지 대기. 이 설정 없으면 JDBC Batch Buffer 사용 안하는 것.
2. DB 사이드 : Redo Log Buffer (DB 트랜잭션 로그)
- 쿼리가 DB로 전달된 후 Redo Log Buffer에 대기
- commit() 시점에 실제 데이터 파일에 반영됨
- 예시: 오라클, MySQL InnoDB는 commit 전에 변경 내용을 Redo Log에 기록 후 반영
JDBC는 Java Database Connectivity의 약자로 데이터베이스와 통신하기 위한 API
추상화, 의존성 역전의 개념
사용하는 데이터베이스만 교체해도 사용할 수 있게 만든 인터페이스
JDBC는 데이터베이스의 종류에 상관없이 똑같은 코드로 해결할 수 있음
인터페이스 : JDBC
구현체 : 각 데이터베이스에 맞는 드라이버
ORM(Object Relational Mapping)
- 데이터베이스 결과와 코드상의 객체를 매핑하는 기술
- JPA(Java Persistence API) 는 이 중 하나일 뿐
JPA는 데이터베이스와 객체를 매핑하는 기술일 뿐, 내부적으로는 데이터베이스와의 통신을 위해 JDBC를 사용
또한 JPA도 JDBC와 마찬가지로 인터페이스이기 때문에 구현체가 필요하고, 그 구현체 중 하나가 Hibernate 인 것.(가장 널리 사용됨)
Spring Data JPA란?
- JPA를 더 쉽게 사용할 수 있도록 도와주는 스프링의 하위 프로젝트
- JpaRepository<T, ID> 인터페이스를 상속받으면 기본적인 CRUD 기능을 자동으로 제공
- 메서드 이름만으로 쿼리를 생성하는 "쿼리 메서드" 기능 지원 (findByName, findByEmail 등)
흐름
0. Spring Data JPA(있으면). jpaRepository.findBy~
1. JPA (인터페이스)
2. Hibernate (구현체)
3. JBDC (인터페이스)
4. 각 DB 드라이버(구현체)
참고
https://velog.io/@pppp0722/JDBC-JPA-Hibernate-Spring-Data-JPA-%EC%B0%A8%EC%9D%B4
'SpringBoot' 카테고리의 다른 글
batch insert하기 Spring Data JPA saveAll() vs 네이티브 쿼리 (0) | 2025.04.04 |
---|---|
@GroovyASTTransformation 컴파일타임 조작, 어노테이션 동적 적용 (0) | 2025.02.24 |
호출 순서 TestBootstrapInitializer, TestExecutionListener, setupSpec, ApplicationContextInitializer (0) | 2025.01.10 |
삽질. groovy, spock 통합테스트 적용 (0) | 2024.12.19 |
spock @LocalServerPort 할당 시점, setup, setupSpec 차이 (0) | 2024.11.20 |
댓글