엔티티 매니저 팩토리는 생성 비용이 아주 크다. 따라서 엔티티 매니저 팩토리는 하나만 생성해서 애플리케이션 전체에서 공유해야한다.
엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 쓰레드 간에 공유하거나 재사용해서는 안된다(한번 쓰고 버려야한다).
JPA의 모든 데이터 변경은 트랜잭션 안에서 실행된다.
JPQL(Java Persistence Query Language)
단건 조회는 다음과 같은 코드로 간단하게 처리할 수 있다.
Member findMember = em.find(Member.class , id);
그런데 하나 이상의 목록을 여러 조건을 붙여서 검색하려면 어떻게 해야할까?
JPA는 엔티티 객체를 중심으로 개발하므로 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야한다.
애플리케이션이 필요한 데이터만 데이터베이스에서 불러오려면 결국 검색 조건이 포함된 SQL을 사용해야 하는데, JPA는 JPQL이라는 쿼리 언어로 이런 문제를 해결한다.
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
JPQL은 SQL을 추상화한 객체지향 쿼리 언어이다. SQL과 문법이 거의 유사하지만 큰 차이가 있다.
SQL은 데이터베이스 테이블을 대상으로 쿼리하지만, JPQL은 엔티티 객체를 대상으로 쿼리한다. 위 예제의 select m from Member m에서
from Member는 엔티티 객체를 의미하는 것이지 데이터베이스 테이블을 의미하는 것이 아니다. JPQL은 데이터베이스 테이블을 전혀 알지 못한다. 또한 JPQL은 SQL과 달리 대소문자를 엄격히 구분한다.
EntityManager
persistence 설정 정보를 읽어서 EntityManagerFactory 를 생성한다. EntityManagerFactory는 EntityManager를 생성한다.
EntityManager는 DB와 관련된 작업을 수행하기 위한 인터페이스. 이 EntityManager를 통해서 객체를 조회하고 저장하는 등의 작업을 할 수 있다.
PersistenceContext
Java Program과 DB 사이의 중간 계층. 개념적으로는 엔티티를 영구 저장하는 환경을 말한다.
다음과 같은 이점을 제공한다.
1차 캐시
객체의 동일성 보장
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member = new Member(1L, "hello");
em.persist(member);
Member findMember1 = em.find(Member.class, 1L);
Member findMember2 = em.find(Member.class, 1L); // 같은 객체를 가리키고 있다.
tx.commit();
em.close();
emf.close();
}
}
트랜잭션을 지원하는 쓰기 지연
변경 감지(Dirty Checking)
지연로딩
EntityManager를 통해 PersistenceContext에 접근할 수 있다.
J2SE 환경에서는 em과 pc가 1대1 관계.
J2EE, 스프링 프레임워크 같은 컨테이너 환경에서는 em과 pc가 다대일
EntityManager와 PersistenceContext
기본적으로…
SessionImpl이라는 구현체가 EntityManager를 구현하고 있음.
SessionImpl은 PersistenceContext라는 타입을 멤버로 가지고 있다.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member hello1 = new Member(1L, "hello1");
Member hello2 = new Member(2L, "hello2");
em.persist(hello1);
em.persist(hello2); // 여기 브레이크 포인트를 찍으면
tx.commit();
em.close();
emf.close();
}
}
위처럼 persistenceContext안에 있는 해시맵(entitiesByKey) 안에 “hello1”이라는 이름을 가진 멤버 객체를 저장하고 있는 것을 확인할 수 있다.
플러시(flush)
플러시(flush())는 영속성 컨텍스트의 변경사항을 데이터베이스에 반영한다.
플러시를 실행하면 구체적으로 다음과 같은 일이 일어난다.
영속성 컨텍스트 내 모든 엔티티를 스냅샷과 비교해 변경된 엔티티를 찾는다.
수정된 엔티티가 있다면 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다.
쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.
플러시는 다음과 같은 상황에서 호출된다.
직접 호출(em.flush())
트랜잭션 커밋 시
JPQL 쿼리 실행 시
JPQL 쿼리를 실행할 때 플러시가 호출되는 이유는 다음과 같다.
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
memberA, B, C는 영속성 컨텍스트에는 있지만 아직 데이터베이스에 반영되지 않았다. 근데 여기서 조회 쿼리를 실행하면
당연히 memberA, B, C를 찾을 수 없다. 그렇기 때문에 JPQL을 실행하면 그 전에 플러시가 자동 호출되도록 설계된 것이다.
플러시 모드를 직접 지정하려면 javax.persistence.FlushModeType을 사용한다.
FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시(기본값)
FlushModeType.COMMIT: 커밋할 때만 플러시
EntityStatus
비영속(TRANSIENT): 영속성 컨텍스트와 단 한 번도 연관관계를 맺은 적이 없는 상태.
영속(PERSISTENT): 영속성 컨텍스트가 관리하는 상태. 식별자가 반드시 있어야한다. Auto_increment를 사용하는 경우 persist(entity)를 호출하면 바로 쿼리를 날려서 식별자를 얻어옴.
준영속(DETACHED): 식별자는 있지만 영속성 컨텍스트에서 관리하지 않는 상태. 영속성 컨텍스트가 제공하는 어떤 서비스도 받을 수 없다.
삭제(DELETED): 영속성 컨텍스트에서 삭제된 상태. 데이터베이스에서도 삭제한다.
ex) saveEvent에서 엔티티 상태에 따른 처리
merge
준영속 상태의 엔티티를 영속 상태로 만든다. 정확히 말하면 새로운 영속 상태의 엔티티를 반환한다.
데이터베이스 방언
JPA는 특정 데이터베이스에 종속되지 않는다. 각각의 RDBMS 제품은 SQL 문법과 함수가 조금씩 다름.
ex) MySQL - VARCHAR, LIMIT, Oracle - VARCHAR2, ROWNUM 등
dialect 속성에 DB 제품을 명시해주면 해당 DB의 문법으로 쿼리 매핑 (Dialect 인터페이스에 접근하면 각 DB의 Dialect 구현체 사용)
JPA 구동 방식
❗️ 주의점
JPQL(Java Persistence Query Language)
단건 조회는 다음과 같은 코드로 간단하게 처리할 수 있다.
그런데 하나 이상의 목록을 여러 조건을 붙여서 검색하려면 어떻게 해야할까?
JPA는 엔티티 객체를 중심으로 개발하므로 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야한다.
애플리케이션이 필요한 데이터만 데이터베이스에서 불러오려면 결국 검색 조건이 포함된 SQL을 사용해야 하는데, JPA는 JPQL이라는 쿼리 언어로 이런 문제를 해결한다.
JPQL은 SQL을 추상화한 객체지향 쿼리 언어이다. SQL과 문법이 거의 유사하지만 큰 차이가 있다.
SQL은 데이터베이스 테이블을 대상으로 쿼리하지만, JPQL은 엔티티 객체를 대상으로 쿼리한다. 위 예제의
select m from Member m
에서from Member
는 엔티티 객체를 의미하는 것이지 데이터베이스 테이블을 의미하는 것이 아니다. JPQL은 데이터베이스 테이블을 전혀 알지 못한다. 또한 JPQL은 SQL과 달리 대소문자를 엄격히 구분한다.EntityManager
persistence 설정 정보를 읽어서
EntityManagerFactory
를 생성한다. EntityManagerFactory는 EntityManager를 생성한다.EntityManager는 DB와 관련된 작업을 수행하기 위한 인터페이스. 이 EntityManager를 통해서 객체를 조회하고 저장하는 등의 작업을 할 수 있다.
PersistenceContext
Java Program과 DB 사이의 중간 계층. 개념적으로는 엔티티를 영구 저장하는 환경을 말한다.
다음과 같은 이점을 제공한다.
EntityManager를 통해 PersistenceContext에 접근할 수 있다.
EntityManager와 PersistenceContext
기본적으로…
SessionImpl이라는 구현체가 EntityManager를 구현하고 있음.
SessionImpl은 PersistenceContext라는 타입을 멤버로 가지고 있다.
위처럼 persistenceContext안에 있는 해시맵(entitiesByKey) 안에 “hello1”이라는 이름을 가진 멤버 객체를 저장하고 있는 것을 확인할 수 있다.
플러시(flush)
플러시(flush())는 영속성 컨텍스트의 변경사항을 데이터베이스에 반영한다.
플러시를 실행하면 구체적으로 다음과 같은 일이 일어난다.
플러시는 다음과 같은 상황에서 호출된다.
JPQL 쿼리를 실행할 때 플러시가 호출되는 이유는 다음과 같다.
memberA, B, C는 영속성 컨텍스트에는 있지만 아직 데이터베이스에 반영되지 않았다. 근데 여기서 조회 쿼리를 실행하면 당연히 memberA, B, C를 찾을 수 없다. 그렇기 때문에 JPQL을 실행하면 그 전에 플러시가 자동 호출되도록 설계된 것이다.
플러시 모드를 직접 지정하려면 javax.persistence.FlushModeType을 사용한다.
EntityStatus
ex) saveEvent에서 엔티티 상태에 따른 처리
merge
준영속 상태의 엔티티를 영속 상태로 만든다. 정확히 말하면 새로운 영속 상태의 엔티티를 반환한다.
병합과정