자바/JPA 프로그래밍
2. JPA 시작, 내부 구조
hooooolly
2024. 11. 3. 18:30
🔽데이터베이스 방언
- 방언: SQL 표준을 지키지 않는 특정 데이터베이스만의 고유한 기능
- JPA는 자동으로 방언을 해석해서 특정 데이터베이스에 종속하지 않는다
- 데이터베이스 변경 시 보수가 용이하다
- 하이버네이트는 40가지 이상의 데이터베이스 방언 지원
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
🔽JPQL
- JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
- JPQL은 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리 (SQL은 데이터베이스 테이블을 대상으로 쿼리)
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존 X
🔽JPA 내부 동작
- 영속성 컨텍스트
- 엔티티를 영구 저장하는 환경
- 엔티티 매니저를 통해 접근한다 (엔티티 매니저 내부에 영속성 컨텍스트 공간이 생긴다)
- 생명 주기
1. 비영속
- JPA와 전혀 관계없는 객체만 생성한 상태
2. 영속
- em.persist를 통해 엔티티 매니저 안의 영속 컨텍스트에 객체를 저장한 상태로 객체는 관리될 준비가 됨
3. 준영속
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리
- 영속성 컨텍스트가 제공하는 기능을 사용 못함
- em.detach : 특정 엔티티만 준영속 상태로 전환
- em.clear : 영속성 컨텍스트를 완전히 초기화
- em.close : 영속성 컨텍스트를 종
4. 삭제
- em.remove 객체를 삭제한 상태
package hellojpa;
import jakarta.persistence.*;
public class JpaMain {
public static void main(String[] args) {
//엔티티 매니저 팩토리 생성
//persistence.xml 설정파일의 <persistence-unit name="hello">로 설정 정보를 조회
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
//엔티티 매니저 생성
//엔티티 매니저는 쓰레드 간에 공유하면 안된다 (사용하고 버린다)
EntityManager em = emf.createEntityManager();
//데이터를 변경하는 모든 작업은 꼭 transcation안에서 작업을 해아한다
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member = null;
try {
//insert
member = new Member();
member.setId(2L);
member.setName("Hello2");
em.persist(member); //엔티티 저장
//조회, 수정
Member find = em.find(Member.class, 2L);
find.setName("JPA"); //JPA를 통해서 엔티티를 가져오면 엔티티 매니저가 관리함
//트랜잭션 커밋 시점에 변경이 됐는지 안됐는지 체크함
//변경사항이 있으면 update 쿼리를 먼저 만들어서 날리고 트랜잭션 커밋함
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
🔽 영속성 컨텍스트의 이점
➡️ 1차 캐시
- DB까지 조회하지 않고 1차 캐시에서 조회하고 1차 캐시에 없으면 DB에서 조회해 온 엔티티를 1차 캐시에 저장한다
- 이때, 고객의 요청마다 하나씩 연결되는 영속성 컨텍스트(엔티티 매니저)는 비즈니스가 끝나면 지우면서 1차 캐시도 사라짐
- 데이터베이스 하나의 트랜잭션 안에서만 조회 시 효과가 있음
➡️ 영속 엔티티의 동일성 보장
- 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭 션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
➡️트랜잭션을 지원하는 쓰기 지연
tx.begin(); // [트랜잭션] 시작
try {
Member memberA = new Member(10L, "A");
Member memberB = new Member(11L, "B");
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 한꺼번에 모아서 INSERT SQL을 보낸다.
//버퍼링 같은 기능
tx.commit(); // [트랜잭션] 커밋
➡️변경 감지/Dirty Checking
- 커밋하는 순간 JPA 내부적으로 flush를 호출한다.
- 이때 엔티티와 스냅샷(최초의 상태)을 모두 비교한다.
- 변경 내용이 있으면 쓰기 지연 SQL 저장소에 update 쿼리문을 생성한다.
- 데이터베이스에 update 쿼리문을 실행한다.
- 커밋한다.
🔽플러시
- 영속성 컨텍스트의 변경내용을 데이터베이스에 반영(동기화)
- 플러시는 직접 호출하거나 (em.flush) 트랜잭션 커밋이나 JPQL 쿼리를 실행할 때 자동 호출된다
- 플러시 옵션을 바꿔서 커밋할 때만 플러시를 할 수 있다
em.setFlushMode(FlushModeType.COMMIT);
em.setFlushMode(FlushModeType.AUTO);