JPA에서 가장 중요한 2가지
- 객체와 관계형 데이터베이스 매핑 (ORM)
- 영속성 컨텍스트
영속성 컨텍스트
- 영속성 컨텍스트는 "엔티티를 영구 저장하는 환경"이다.
- EntityManager.persist(entity);
- DB에 바로저장하기보다는 영속성 컨텍스트를 통해 엔티티를 영속화 시킨다는 의미다.
- 영속성 컨텍스트는 애플리케이션과 DB의 중간 계층이다.
- 영속성 컨텍스트는 논리적 개념이다. 눈에 보이지 않는다.
- 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있다.
🔍 J2SE 환경
- 엔티티 매니저와 영속성 컨텍스트는 1:1 관계
🔍 J2EE, 스프링 프레임워크 같은 컨테이너 환경
- 엔티티 매니저와 영속성 컨텍스트는 N:1 관계
1. 엔티티 매니저 팩토리와 엔티티 매니저
- 엔티티 매니저 팩토리는 웹 애플리케이션에서 딱 1개만 존재한다.
- 엔티티 매니저는 고객의 요청만큼 생성된다.
- 영속성 컨텍스트는 "엔티티를 영구 저장하는 환경"이다.
- EntityManager.persist(entity);
- 엔티티 매니저 팩토리는 고객의 요청에 따라 엔티티 매니저를 생성한다.
- 엔티티 매니저는 내부적으로 데이터베이스 커넥션 풀을 사용하여 DB에 접근한다.
🧷 참고. 커넥션 풀 (connection pool)
커넥션 풀 (Connection Pool)
컨넥션 풀이란, WAS(웹 컨테이너)가 실행 될 때 DB 연결을 위해 미리 일정 수의 Connection 객체를 만들어 Pool에 담아 뒀다가 고객의 요청이 발생하면 Pool에서 생성되어 있는 Connection 객체를 넘겨주고 고객이 사용이 끝나면 Connection 객체를 다시 Pool에 반환하여 보관하는 기법이다.
컨넥션 풀을
1. 커넥션 풀 동작 과정

- 고객이 DB를 사용하기 위해 Connection을 요청한다.
- Connection Pool에서 사용되지 않고 있는 Connection 객체를 제공한다.
- 고객이 Connection 객체 사용 완료가 끝나면 Connection Pool로 반환한다.
2. 웹 애플리케이션에서 커넥션 풀을 사용해서 얻는 이점
🔍 서버 부하를 줄여준다.
- 자바에서 DB Connection을 맺는 과정은 부하가 많이 걸리는 작업이다.
- 따라서 많은 사람들이 동시에 DB 커넥션을 요구하는 경우 서버가 죽어버리는 문제가 발생할 수 있다.
- 이러한 문제를 해결하기 위해 미리 Connection 객체를 생성하고 재활용하여 서버에 부하를 줄여주는 용으로 사용한다.
🔍 서버의 한정적인 자원을 효율적으로 사용할 수 있다.
- 서버는 한정적인 자원을 가지고 있다.
- 요청이 올 때 마다 무제한의 Connection을 생성하게 된다면 전체 시스템 서능에 문제가 생길 수 있다.
- 이러한 문제를 해결하기 위해 커넥션 풀에 미리 정해진 숫자의 Connection 객체를 생성하여 관리한다.
🔍 고객이 기다려야하는 시간을 줄일 수 있다.
- 각 사용자에 대한 데이터 베이스 연결 요청은 시간과 비용이 비싸다.
- Connection Pool에서는 Connectiuon이 생성 된 후 Pool에 배치하고 다시 사용되므로 새 연결을 설정할 필요가 없다.
- 따라서 데이터베이스에 대한 연결을 설정하기 위해 사용자가 기다려야하는 시간을 줄일 수 있다.
참고로 쓰레드 풀 (Thread Pool) 과 커넥션 풀 (Connection Pool)은 서로 다르다.
2. 엔티티의 생명주기
- 비영속(new/transient)
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 영속 (managed)
- 영속성 컨텍스트에 관리되는 상태
- 준영속 (detached)
- 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제 (removed)
- 삭제된 상태
🔍 비영속
- JPA에 관계없이 그냥 객체 생성만 된 상태
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId(100L);
member.setName("HelloWorld");
🔍영속
- em.persist(Etity객체) 가 된 상태
- 엔티티가 영속 컨텍스트에 포함된 상태
- 영속 상태가 되었다고 SQL쿼리가 바로 DB에 날라가는 것은 아니다.
- 엔티티 트랜잭션의 commit() 메서드가 호출되어야 그제서야 SQL 쿼리가 DB로 넘어간다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId(100L);
member.setName("HelloWorld");
// 객체를 저장한 상태 (영속)
em.persist(member);
🔍 준영속
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
// 회원 엔티티를 영속성 컨텍스트에서 분리(준영속 상태)
em.detach(member);
- 준영속은 영속성 컨텍스트에서 해당 엔티티 객체를 삭제하는 것이다.
- 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.
- 한마디로 영속상태를 삭제시키는 것이다.
🔍 삭제
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
// 객체를 삭제한 상태 (삭제)
em.remove(member);
- 실제 DB 삭제를 요청한 상태다.
3. 영속성 컨텍스트의 이점
- 1차 캐시
- 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연 (transcational write-behind)
- 변경 감지 (Dirty Checking)
- 지연 로딩 (Lazy Loading)
🔍 엔티티 조회, 1차 캐시 (2차 캐시 도입 전)
- 1차 캐시는 영속성 컨텍스트 내부에 엔티티를 보관하는 저장소다.
- 영속성 컨텍스트를 엔티티 매니저로 이해해도 된다. 물론 미묘한 차이가 있긴 하다.
- 일반적으로 트랜잭션을 시작하고 종료될 때 까지만 1차 캐시가 유효하다.
- 엔티티 매니저는 데이터베이스 트랜잭션 단위로 생성이 된다. 트랜잭션이 끝나면 엔티티 매니저도 같이 종료된다. 그러면 1차 캐시도 같이 날라간다.
- 1차 캐시는 데이터베이스의 한 개의 트랜잭션 안에서만 효과가 있다. 애플리케이션이 공유하는 캐시는 2차 캐시라고 부른다.
- 그래서 1차 캐시로 얻는 성능의 이점이 크지 않다.
✔ 1차 캐시에서 조회
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member(200L, "memberA");
// 1차 캐시에 저장됨
em.persist(member);
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, 200);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
- find로 객체를 찾을 때 바로 DB에서 찾지 않고 1차 캐시를 먼저 조회한다.
- 1차 캐시의 수명은 트랜잭션의 수명과 같다.
- persist로 저장된 객체는 영속성의 1차 캐시에 저장된다.
- 트랜잭션이 끝나기 전에 1차 캐시에 저장된 값을 조회하면1차 캐시에 저장된 값을 반환 받는다.
- 이 때 SQL SELECT 구문이 DB에 보내지 않고 바로 애플리케이션 단계에서만 일어난다.
- 결론적으로 1차 캐시에 존재하는 값을 조회할 경우 SQL SELECT 구문을 생성하지 않고 1차 캐시에서 값을 반환 받는다.
✔ 데이터베이스에서 조회 (DB에는 존재하지만 1차 캐시에는 없는 값을 조회한 경우)
- 1차 캐시에 원하는 값이 없기 때문에 DB에서 조회를 한다.
- DB에서 찾은 값을 1차 캐시에 저장한다.
- 1차 캐시에 저장된 값을 반환한다.
🧷 참고. 2차 캐시
2차 캐시
- 2차 캐시는 애플리케이션 범위의 캐시(공유 캐시)다.
- 하이버네이트를 포함한 대부분의 JPA 구현체들은 2차 캐시를 지원한다.
- 공유 캐시의 생명 주기는 애플리케이션이 시작될 때부터 종류될 때 까지 유지된다.
- 공유 캐시를 통해 애플리케이션 조회 성능을 향상 시킬 수 있다.
- 네트워크를 통해 데이터베이스를 접근하는 시간 비용은 애플리케이션 서버에서 내부 메모리에 접근하는 시간 비용보다 수만에서 수십만 배 이상 비싸다.
- 따라서 조회한 데이터를 메모리에 캐시해서 데이터 베이스 접근 횟수를 줄이면 애플리케이션 성능을 개선할 수 있다.
- 1차 캐시는 영속석 컨텍스트 내부에 존재하지만, 2차 캐시는 영속성 컨텍스트 외부에 존재한다.
- 1차 캐시는 트랜잭션이 시작부터 종료될 때 까지만 유효하기 때문에 데이터 베이스 접근 횟수를 획기적으로 줄일 수 없다.
1. 2차 캐시 적용 전

2. 2차 캐시 적용 후

3. 2차 캐시 동작 방식

- 동작 방식
- 엔티티 매니저는 최초 조회를 2차 캐시에 한다. 2차 캐시에 존재하지 않는다면 DB를 조회한다.
- 조회의 결과 값을 2차 캐시에 저장하고, 해당 엔티티의 복사 본을 1차 캐시인 영속성 컨텍스트에 반환한다.
- 동일 조회가 다른 영속성 컨텍스트에 들어오면 2차 캐시에 존재하는 엔티티를 복사하여 전달한다.
- 2차 캐시는 동시성을 극대화하기 위해 캐시한 객체의 본사본을 만들어서 반환한다.
- 만약 캐시한 원본 객체를 반환하면 동시에 여러 곳에서 해당 엔티티를 접근할 경우 문제가 생길 수 있기에 락을 걸어야 하므로 동시성이 떨어질 수 있다.
- 락에 비하면 객체를 복사하는 비용이 더 저렴하다.
- 알아야할 점은 2차 캐시는 데이터베이스의 기본 키를 기준으로 캐시하지만 영속성 컨텍스트가 다를 경우 객체의 동일성을 보장하지 않는다. (복사본을 반환하기 때문)
🔍 영속 엔티티의 동일성 보장
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
Member a = em.find(Member.class, 100L);
Member b = em.find(Member.class, 100L);
System.out.println(a == b); // true
- JPA는 영속 엔티티의 동일성을 보장한다. (1차 캐시가 있기에 가능하다.)
- 같은 트랜잭션 안에서 동일한 객체를 조회할 때 데이버베이스에서 가져오는 것이 아닌 애플리케이션 차원에서 객체를 제공한다.
🔍엔티티 등록 - 트랜잭션을 지원하는 쓰기 지연을 지원한다.
커밋 전까지는 SQL 구문을 데이터베이스에 보내지 않고 '쓰기 지연 SQL 저장소'에 저장한다.
커밋하는 순간 데이터 베이스에 SQL 구문을 보낸다.
한마디로 버퍼링 기능이다.
쓰기 지연을 통해서 쿼리를 여러번 날리지 않고 최적화가 가능하다.
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 비영속
Member member1 = new Member(150L, "A");
Member member2 = new Member(150L, "B");
// 영속
em.persist(member1);
em.persist(member2); // 아직 엔티티 등록이 수행되지 않는다. (쓰기 지연)
System.out.println("=======================================");
tx.commit(); // 이 때 한꺼번에 엔티티 등록이 수행된다.
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
/* 출력 결과 */
=======================================
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
- 출력 결과를 보면 알 수 있듯이 커밋전에는 SQL 구문을 데이터베이스에 보내지 않는다.
✔ commit 전
- memberA와 memberB는 둘 다 쓰기지연 SQL 저장소에 저장되어 있고 실제 DB에 적용(저장)이 안된 상태다.
✔ commit 후
- commit 시점에 '쓰기 지연 SQL 저장소'에 저장된 쿼리들을 다 실행시켜서 DB에 적용한다.
🔍 엔티티 수정 - 변경 감지 (Dirty Checking)
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member findMember = em.find(Member.class, 150L);
findMember.setName("ABC");
System.out.println("=======================================");
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
/* 출력 결과 */
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
=======================================
Hibernate:
/* update
hellojpa.Member */ update
Member
set
name=?
where
id=?
- 1차 캐시 안에는 @Id / Entity / 스냅샷이 존재한다.
- 여기서 스냅샷은 최초로 영속성 컨텍스트(1차 캐시)에 들어온 순간 스냅샷을 찍어서 저장한다.
- JPA는 트랜잭션이 커밋(commit)되는 순간 엔티티와 스냅샷을 모두 비교한다.
- 변경된 것이 있을 경우 쓰기 지연 SQL 저장소에 업데이트 쿼리를 저장하고 수행한다.
🔍 엔티티 삭제
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, “memberA");
em.remove(memberA); //엔티티 삭제
플러시
- 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 과정이다.
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 것이다.
- 영속성 컨텍스트의 '쓰기 지연 SQL 저장소'의 쿼리들을 DB에 전송한다.
1. 플러시 발생시 일어나는 과정
- 플러시 발생시 동작 순서
- 1. 변경 감지가 일어난다.
- 2. 수정된 엔티티를 '쓰기 지연 SQL 저장소'에 등록한다.
- 3. '쓰기 지연 SQL 저장소'의 쿼리를 데이터베이스에 전송한다. (등록 / 수정/ 삭제 쿼리)
- 한마디로 플러시는 변경 감지가 일어나서 '쓰기 지연 SQL 저장소' 쿼리가 바뀌고, 바뀐 쿼리들이 DB에 반영되는 과정이다.
2. 영속성 컨텍스트를 플러시하는 방법
- em.flush() - 직접 호출
- 트랜잭션 커밋 - 플러시 자동 호출
- JPQL 쿼리 실행 - 플러시 자동 호출
- JPQL 쿼리 실행시 플러시가 자동으로 호출되는 이유
- JPQL 쿼리를 실행하는 시점에서 영속화 컨텍스트에 등록한 엔티티들이 조회가 안되는 경우를 막기 위해 JPA는 JPQL 쿼리를 수행하기 전에 flush를 실행해서 DB와 영속성 컨텍스트간에 동기화를 해준다.
- JPQL 쿼리 실행시 플러시가 자동으로 호출되는 이유
- 개발자가 플러시를 직접 호출하는 경우는 별로 없지만 테스트하기 위해서 알아놔야 한다.
여기서 em 인스턴스는 EntityManager 객체다.
3. 플러시 모드 옵션
- em.setFlushMode(FlushModeType.AUTO)
- FlushModeType.AUTO : 커밋이나 쿼리를 실핼할 때 플러시가 호출된다. (기본값)
- FlushModeType.COMMIT : 커밋할 때만 플러시가 호출된다.
- 주로 기본값만 사용한다. 그러므로 손 댈일이 없다.
여기서 em 인스턴스는 EntityManager 객체다.
준영속 상태
- 준영속 상태는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)된 상태를 의미한다.
- 준영속 상태가 되면 영속성 컨텍스트 기능을 사용할 수 없기에 commit()이 되어도 flush()가 호출되지 않는다.
- 준영속 상태를 직접 쓸 일이 거의 없다.
1. 준영속 상태 만드는 방법
- em.detach(entity)
- 특정 엔티티만 준영속 상태로 전환한다.
- em.clear()
- 영속성 컨텍스트를 완전히 초기화한다.
- em.close()
- 영속성 컨텍스트를 종료시킨다.
여기서 em 인스턴스는 EntityManager 객체다.
👀 참고 자료
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
https://willseungh0.tistory.com/77
[JPA] 1차 캐시 vs 2차 캐시
JPA에 대해서 헷갈렸던 개념들을 위주로 정리하는 글입니다. 1차 캐시와 2차 캐시 네트워크를 통해 데이터베이스에 접근하는 시간 비용은 애플리케이션 서버 내부 메모리에 접근하는 시간보다
willseungh0.tistory.com
https://catsbi.oopy.io/523e7c47-e9ab-43ef-bc19-3e527ecff3a7
영속성 관리 - 내부 동작 방식
영속성 컨텍스트 1
catsbi.oopy.io
https://programmer93.tistory.com/74
커넥션 풀(Connection pool)이란?
커넥션 풀(Connection Pool)이란? WAS(웹 컨테이너)가 실행 될 때 DB연결을 위해 미리 일정수의 connection 객체를 만들어 Pool에 담아 뒀다가 사용자의 요청이 발생하면 Pool에서 생성되어 있는 Connection 객
programmer93.tistory.com
https://dung-beetle.tistory.com/68
mysql을 시작하기 전에 3
1. Thread pool 이란? thread를 미리 생성해놓고 적절하게 관리하는 시스템 2. connection pool이라는 것도 있던데? 개념이 조금 다른데 위키디피아 내용으로 정리한다. #thread_pool vs Connection pool #connect..
dung-beetle.tistory.com
'[JPA] > JPA 프로그래밍 - 기본편' 카테고리의 다른 글
[JPA] 다양한 연관관계 매핑 (0) | 2022.03.29 |
---|---|
[JPA] 연관관계 매핑 기초 (0) | 2022.03.28 |
[JPA] 엔티티 매핑 (0) | 2022.03.27 |
[JPA] JPA 시작하기 (0) | 2022.03.26 |
[JPA] SQL 중심적인 개발의 문제점과 JPA 소개 (0) | 2022.03.26 |