JPA 등장배경
객체지향적인 프로그래밍과 관계형 데이터베이스(RDB)는 달라도 너무 다르다.
상속, 다형성, 캡슐화, 참조 구조가 객체 세계에서는 너무나 자연스럽고 당연하지만 SQL로는 표현할 수 없다.
예시로, 객체는 필드에 다른 객체를 참조하지만, DB는 외래키로 연결한다. 즉, 객체 모델과 테이블 모델이 매핑이 안 맞아서 개발자의 부담이 증가하게 되었다.
JPA가 등장하기 전, 우리는 JDBC 기반으로 SQL을 직접 작성했고, DB가 변경되면 코드를 수동으로 수정해야하고 매우 반복적인 수작업을 필요로 했다. 따라서 이에 대한 해결책으로 ORM이 등장했다.
✅ ORM(Object-Relational Mapping)이란 ?
객체와 RDB 테이블 사이를 자동으로 매핑해주는 기술
객체를 중심으로 개발하고, SQL은 백그라운드에서 자동으로 수행하게 한다.
이러한 ORM 기술 중 하나가 바로 JPA이다.
JPA
자바 객체를 관계형 데이터베이스에 매핑(Persistence)하기 위한 자바 표준 ORM(Object Relational Mapping) 기술
JPA를 사용하면서 얻게 된 이점
- SQL을 직접 작성하지 않아도 되므로 생상선 향상
- 객체와 테이블의 불일치 문제 해결 (ORM 매핑)
- 트랜잭션, 영속성 컨텍스트(Persistence Context) 제공
- Hibernate Dialect 설정을 통해 다양한 DBMS를 지원
- JPQL, Criteria API, QueryDSL 등 강력한 조회 기능 제공

대표적인 JPA 구현체 : Hibernate, EclipseLink, DataNucleus
Hibernate (하이버네이트) - 가장 대표적이고 널리 쓰이는 구현체
Spring Boot에서는 보통 Spring Data JPA + Hibernate
조합을 주로 사용한다.
또한 hibernate.dialect
는 어떤 데이터베이스를 사용 중인지 알려주는 설정으로, 각 DBMS에 맞는 SQL 문법으로 생성한다.
영속성 컨텍스트
엔티티 객체를 관리하는 JPA의 1차 캐시 공간
영속성
: 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성컨텍스트
: "주변 환경"이나 "상황"을 의미
영속성 컨텍스트
: 엔티티 객체들을 메모리 상에서 보관하고 관리하는, 눈에 보이지 않는 논리적인 저장소
🤔 잠깐 ! 캐시가 뭐야 ?
캐시는 자주 사용하는 데이터를 임시로 저장해두는 저장소를 의미한다.
🧺 쉽게 이해해보기 !
쉽게 말하자면 영속성 컨텍스트는 장바구니인 것이다. JPA는 엔티티 객체들을 이 장바구니(영속성 컨텍스트)에 담아두고, 필요할 때 빠르게 꺼내서 사용한다.
- 이 장바구니(영속성 컨텍스트) 안에는 DB에서 조회한 엔티티 객체들이 보관되어 있음
- 그리고 이 객체들을 수정하거나 삭제하면, JPA가 나중에 DB에 반영되도록 자동으로 감지(변경 감지) 함
- 다시 같은 데이터를 요청하면 DB에 안 가고 장바구니에서 꺼내줌 → 1차 캐시
Entity 객체를 영속성 상태로 관리하는 일종의 캐시 역할을 하는 공간으로 여기에 저장된 Entity는 데이터베이스와 자동으로 동기화되며 같은 트랜잭션 내에서는 동일한 객체가 유지된다.
엔티티 매니저(EntityManager)
EntityManager는 JPA에서 모든 DB 작업을 처리하는 중간 관리자다.
JPA에서 데이터베이스와의 모든 상호작용을 담당하는 핵심 인터페이스이자 객체
즉, 엔티티를 조회, 저장, 수정, 삭제, flush, clear 등의 작업을 한다.
EntityManager의 주요 역할
역할 | 설명 |
---|---|
1. 영속성 컨텍스트 관리 | - EntityManager 는 영속성 컨텍스트를 생성하고 관리 - 엔티티 객체를 1차 캐시에 저장 |
2. 엔티티의 생명주기 관리 | - 엔티티의 상태(비영속, 영속, 준영속, 삭제)를 관리 - 객체와 DB 상태를 동기화 |
3. CRUD 기능 제공 | - persist : 저장 - merge : 수정 병합 - remove : 삭제 - find : 조회 |
4. 트랜잭션 관리 지원 | - 트랜잭션 경계를 제어 - 커밋 시 flush() 를 통해 변경 사항을 DB에 반영 |
5. JPQL 실행 | - 객체 중심 쿼리인 JPQL을 실행 - JPQL을 SQL로 변환하여 DB와 통신 |
EntityManager
는 JPA에서 모든 DB 작업을 처리하는 중간 관리자로, 내부에 영속성 컨텍스트
라는 장바구니를 들고 다닌다. 모든 작업은 장바구니에 먼저 담기고 → flush()
나 commit()
시점에 DB로 나간다.
영속성 컨텍스트의 특징
1차 캐시 (First-Level Cache)
동일한 엔티티는 DB에서 여러 번 조회하지 않고 메모리에서 재사용한다. em.find()
등으로 엔티티를 조회하면 DB가 아니라 1차 캐시에서 먼저 찾으므로 같은 엔티티를 여러 번 조회해도 쿼리는 딱 한 번만 나간다.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Member member1 = em.find(Member.class, 1L); // DB 조회 → 1차 캐시에 저장
Member member2 = em.find(Member.class, 1L); // 1차 캐시에서 조회 → DB 쿼리 X
em.getTransaction().commit();
🧺 비유
A라는 상품을 한 번 담았는데,
다시 장바구니에 A를 담으려면 “이미 있어요!” 하며
같은 물건 보여주는 것과 같다.
같 동일성 보장 (Identity Guarantee)
같은 영속성 컨텍스트 안에서는 같은 ID를 가진 엔티티는 항상 "동일 객체" 다.
Member m1 = em.find(Member.class, 1L);
Member m2 = em.find(Member.class, 1L);
System.out.println(m1 == m2); // true
🧺 비유
같은 상품 A를 장바구니에 두 번 담으려 해도
👉 결국 장바구니 안에는 "하나의 A만 있고"
내가 보는 것도 "같은 A"인 것이다 !
쓰기 지연 (Write-behind)
em.persist()
를 호출해도 즉시 INSERT 쿼리를 보내지 않고 영속성 컨텍스트 내부의 쓰기 지연 저장소에 보관하고 있다가 commit()
또는 flush()
시점에 한꺼번에 SQL을 보낸다.
em.getTransaction().begin();
Member member1 = new Member("예은");
Member member2 = new Member("길동");
em.persist(member1); // INSERT 안 나감
em.persist(member2); // INSERT 안 나감
// 여기서 flush 발생
em.getTransaction().commit();
🧺 비유
장바구니에 상품을 여러 개 담고 있다가 “결제 버튼”을 누르면
그제야 마트에 "A 주세요! B 주세요!" 하고 주문 들어가는 거랑 같음!
변경 감지(Dirty Checking)
영속성 컨텍스트가 엔티티의 초기 상태를 저장하고 트랜잭션 커밋 시점에 현재 상태와 비교해 변경 사항이 있는지 확인하는 기능이다.
내부 동작 방식
트랜잭션 커밋 직전, flush()
시점에 변경이 감지된다. JPA는 객체가 처음 영속 상태가 됐을 때의 값을 스냅샷(snapshot) 으로 내부에 저장해둔다. 그리고 나중에 필드 값이 바뀌면 스냅샷과 비교해서 변경 여부를 판단한다.
스냅샨이란 JPA가 엔티티를 감시하기 위해 몰래 찍어둔 사진으로 이해하면 된다. 스냅샷은 영속성 컨텍스트 내부의 1차 캐시 Entry 값으로 저장된다.
@Service
@RequiredArgsConstructor
public class ScheduleService {
private final ScheduleRepository scheduleRepository;
@Transactional
public void updateTodo(Long scheduleId, String newTodo) {
// 1. 엔티티 조회 (영속 상태로 진입)
Schedule schedule = scheduleRepository.findById(scheduleId)
.orElseThrow(() -> new IllegalArgumentException("해당 일정이 없습니다."));
// 2. 필드 값 변경
schedule.setTodo(newTodo); // 이 변경이 영속성 컨텍스트에 의해 감지됨
// 3. 별도 저장 코드 없이도 트랜잭션 커밋 시점에 UPDATE 쿼리 실행됨
}
}
동작 흐름 설명
findById()
→ DB에서 데이터를 조회하여 영속성 컨텍스트에 등록setTodo(newTodo)
→ 필드 값이 변경됨- 변경 감지 발생: JPA가 스냅샷과 비교해서 값이 바뀌었는지 확인
- 트랜잭션이
commit
될 때 → 변경된 내용을 반영하는UPDATE
쿼리가 자동 실행됨
update schedule
set todo = '새로운 할일 제목',
updated_at = '2025-03-31 18:00:00'
where id = 1;
결과적으로 다음과 같은 쿼리가 실행된다.
🧺 비유
장바구니에 사과(가격 1000원)을 담았고,
나중에 사과의 가격을 1200원으로 수정
장바구니(=영속성 컨텍스트)는 "어, 처음 담았을 땐 1000원이었는데 지금은 1200원이네?" 라고 감지하는 것
flush
영속성 컨텍스트의 변경 사항을 DB에 반영하는 행위다. (단, 트랜잭션은 아직 끝나지 않은 상태!)
변경된 엔티티 정보를 SQL로 변환해 데이터베이스에 동기화한다. 트랜잭션 커밋 시 자동으로 실행되지만 특정 시점에 데이터베이스 반영이 필요할 때 수동으로 호출할 수도 있다.
@Transactional
public void updateScheduleTitle(Long id, String newTitle) {
Schedule schedule = entityManager.find(Schedule.class, id);
schedule.setTitle(newTitle); // 값 변경
// 수동 flush → 여기서 UPDATE 쿼리 바로 날아감
entityManager.flush();
System.out.println("DB에 즉시 반영 완료 (flush 후)");
}
🧺 비유
flush()는 "내가 지금까지 장바구니에서 바꾼 거 전부 계산서에 반영해줘!" 라는 요청이다.
flush() → 카운터에 잠깐 보여주고 다시 쇼핑 중 (계산 X)
commit()
→ 결제 완료하고 장바구니 비우기
Entity
데이터베이스에서 Entity란 저장할 수 있는 데이터의 집합을 의미하고, JPA에서 Entity란 데이터베이스의 테이블을 나타내는 클래스를 의미한다.
엔티티가 한 번 JPA에 들어가면, 계속 JPA에 의해 관리된다고 볼 수 있다.
즉, 앞선 장바구니 예시와 이어서 이해하자면, 엔티티는 상품을 의미한다. (JPA는 장바구니 관리자고, 영속성 컨텍스트는 장바구니다)
엔티티 생명주기
JPA가 해당 객체를 어떻게 관리하느냐에 따라 4가지 상태를 가진다.
엔티티 생명주기 4단계
상태 | 설명 |
---|---|
비영속 (Transient) | 영속성 컨텍스트가 모르는 새로운 상태, 데이터베이스와 전혀 연관이 없는 객체 |
영속 (Persistent) | 영속성 컨텍스트에 저장되고 관리되고 있는 상태, 데이터베이스와 동기화되는 상태 |
준영속 (Detached) | 영속성 컨텍스트에 저장되었다가 분리되어 더 이상 기억하지 않는 상태 |
삭제 (Removed) | 영속성 컨텍스트에 의해 삭제로 표시된 상태, 트랜잭션이 끝나면 데이터베이스에서 제거 |

즉, 쉽게 ! 장바구니 예시로 이해한 것을 표로 정리하면 다음과 같다.
실생활 비유 | JPA 구성 요소 | 설명 |
---|---|---|
장바구니 | 영속성 컨텍스트 (Persistence Context) | 엔티티 객체들을 담아두는 1차 캐시 역할을 하는 공간→ 이미 넣은 상품은 DB에 다시 접근하지 않아도 됨 (성능 ↑) |
물건 | 엔티티 (Entity) | DB에 저장할 객체 (예: User , Order , Product ) |
점원 | EntityManager | 장바구니(영속성 컨텍스트)를 관리하는 관리자: 담기, 수정, 삭제, 계산 등 담당 |
계산대 | 데이터베이스(DB) | 최종적으로 저장(계산)되는 장소 |
계산서 | flush() / commit | 트랜잭션 종료 시 변경 사항을 DB에 반영하는 과정 |
가격표 비교 | Dirty Checking (변경 감지) | 엔티티의 변경 여부를 감지해서 실제 DB update가 필요한지 판단 |
담은 상품 목록 | 1차 캐시 | 같은 엔티티를 반복 조회해도 DB에 접근하지 않고 캐시된 데이터를 사용 (성능 최적화) |
밀린 계산 작업지 | 쓰기 지연 (Write-Behind) | persist() 호출 시 DB에 바로 insert 쿼리 보내지 않고flush 시 한꺼번에 쿼리 실행 (성능 최적화, 배치 처리 효과) |
점원이 따르는 규칙서 | JPA (Java Persistence API) | EntityManager가 따르는 명세(API). ORM 프레임워크가 이 명세에 맞춰 동작 |
점원 도구세트 | Hibernate 등 JPA 구현체 | 실제로 JPA 명세에 따라 작동하는 기술 (JPA는 인터페이스, Hibernate는 구현체) |
'Backend > Spring' 카테고리의 다른 글
ServiceHelper를 통한 Service 코드 책임 분리 (0) | 2025.04.01 |
---|---|
BaseTimeEntity와 JPA Auditing: 왜 사용할까? (0) | 2025.04.01 |
[Spring 기초] 일정 관리 앱 만들기 (0) | 2025.03.26 |
JWT 토큰 인증 이란? (0) | 2025.03.25 |
쿠키 세션 토큰 비교하기 (0) | 2025.03.24 |