본문 바로가기
SpringBoot

JPA 영속성 컨텍스트, Dirty Checking, 쓰기 지연, Spring Data JPA, Hibernate, JDBC

by 오렌지마끼야또 2025. 4. 3.
728x90
반응형

 

 

 

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

 

 

728x90
반응형

댓글