# JPA 구동방식
- EntityManagerFactory: 한 Application에 무조건 1개만 존재한다.
- EntityManager: 요청이 들어올 때 마다 하나씩 생성된다.(Transaction 단위로 생존하기 때문에 쓰레드간에 공유X)
- Transaction: 매 수정/조회 마다 Trsanaction사이에서 쿼리가 발생해야한다.
# JPA 실습
## 기본 세팅
public static void main(String[] args){
//SpringApplication.run(HellojpaJpa1Application.class, args);
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try{
Member member = new Member(1L, "member1");
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally{
em.close();
}
emf.close();
}
@Getter
@Setter
@Entity
public class Member{
@Id
private Long id;
private String name;
}
- @Entity: JPA가 관리한 class임을 알려줌.
- @Id: DB의 PrimaryKey와 매핑.
## CURD
- 회원 등록
Member member = new Member(1L, "member1");
em.persist(member);
- 회원 수정
findMember.setName("foundMember");
//em.persist(findMember)
→ persist없이 자동으로 쿼리가 나간다.
- 회원 조회
Member findMember = em.find(Member.class, 1L);
- 회원 삭제
em.remove(findMember);
## JPQL
- 회원 조회시 복잡한 쿼리로 조회하고 싶을 경우가 반드시 발생한다.
- ex) 나이가 18살 이상인 회원을 검색, ID가 2 이상인 회원을 검색.. 등
- 문제는, 특정 데이터베이스마다 SQL문이 달라진다.
- ex) MYSQL의 경우 LIMIT으로 페이징, Oracle의 경우 ROWNU로 페이징
- 특정 DB에 종속된 SQL이 아니라 객체에 종속된 쿼리문을 만들기 위해 JPQL을 사용한다. 즉, 개체지향 SQL이다.
- JPQL을 사용하면 JPA가 JPQL을 DB에 맞게 해석하여 SQL쿼리를 DB에 날린다.
em.createQuery("select m from Member as m where m.name = 'test'", Member.class)
.setFirstResult(5)
.setMaxResults(10)
.getResultList();
→ 이름이 test인 사람들 중 5번째 인 사람부터 10명을 가져오는 복잡한 쿼리
# JPA 내부 구조
## 엔티티 매니터 팩토리와 엔티티 매니저
- 요청이 들어올 때 마다 EntityManagerFactory를 통해 EntityManager를 생성하고, EntityManager은 내부적으로 DB Connection을 사용하여 DB를 사용한다.
## 영속성 컨텍스트
- JPA의 핵심 개념 중 하나다.
- “Entity를 영구 저장하는 환경”이라는 뜻을 갖고 있다.
- 영속성 컨텍스트는 논리적인 개념으로, 눈에 보이지 않는다. 엔티티 매니터를 통해서 영속성 컨텍스트에 접근할 수 있다.
EmtityManager.persist(entity);
→ DB에 저장한다는 뜻이 아니라, 영속성 컨텍스트에 entity를 저장한다는 뜻이다.
## 엔티티의 생명주기
### 비영속(new/tansient)
- 영속성 컨텍스트와 전혀 관계없는 새로 생성된 상태
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
### 영속(managed)
- 영속성 컨텍스트에 의해 관리되는 상태
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
→ 다만, 아직 Query가 날라가지는 않는다. Transaction이 commit되지 않았기 때문이다.
### 준영속(detached)
- 영속성 컨텍스트에 저장되었다가, 이후 분리된 상태
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
### 삭제(removed)
- 객체를 삭제한 상태로, DB에서 삭제하는 동작과 같다.
//객체를 삭제한 상태(삭제)
em.remove(member);
## 영속성 컨텍스트의 이점
### 1. 1차 캐시
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//엔티티를 영속
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
- DB의 pk로 매핑한 attribute가 key가 되고, value는 객체 자체가 된다.
- 이 상태에서 조회할 경우, DB가 아닌 캐시의 값을 조회하기 때문에 SQL이 날라가지 않는다.
Member findMember2 = em.find(Member.class, "member2");
- 1차 캐시에 없기 때문에 DB에 SQL을 날린다. 이후, 얻어온 값을 1차 캐시에 저장하게 된다.
### 2. 영속 엔티티의 동일성(Identity) 보장
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
System.out.println(findMember1 == findMember2);
- 1차 캐시를 통해서 반복 가능한 읽기(Repeatable read)등급의 트랙잭션 격리 수준을 DB가 아닌 애플리케이션 차원에서 제공한다.
### 3. 트랜잭션을 등록하는 쓰기 지연
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
- transaction을 commit할 때 비로소 SQL이 날라가게 된다.
- 버퍼링을 함으로써 DB접근 횟수 자체를 줄여버릴 수 있다.
### 4. 엔티티 변경 감지(Dirty check)
Member member = em.find(Member.class, 150L);
member.setName("ZZZZZ");
tx.commit();
- transaction commit시 flush함수를 호출한다.
- 1차 캐시안에 Entity뿐 아니라 스냅샷(최초 시저의 상태)역시 저장되어 있기 때문에, 스냅샷과 Entity를 비교해서 변경되었으면 Update SQL을 쓰기 지연 SQL저장소에 저장한다.
- 주의할 점은 영속성 컨텍스트를 비우는 것이 아니라, 그냥 DB에 반영하는 것이라는 점이다!
## 플러시(flush)
- 영속성 컨텍스트의 변경내용을 데이터베이스에 반영하는 것을 말한다.
- 절대!!! 영속성 컨텍스트를 비우는 것이 아니다
- 플러시는 아래의 단계로 진행된다.
- 변경감지
- 수정된 엔티티를 쓰기 지연 SQL에 등록한다.
- 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송한다.
### 영속성 컨텍스트를 플러시 하는 방법
- em.flush()를 직접 호출
- Transaction이 commit되기 전에 SQL이 발생하게 된다.
- test할 때 외에는 쓰지 않는다.
- Transaction commit시 자동으로 발생
- JPQL 쿼리 실행시 자동으로 발생
### JPQL 쿼리 실행시 플러시가 자동으로 호출되는 이유
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();
- 만약 자동으로 호출되지 않는다면, memberA~C는 DB에 없으므로 JPQL로 조회가 되지않을 것이다.
- 따라서, 기본 모드가 자동으로 flush를 날리는 것이다.
### 플러시 모드 옵션
em.setFlushMode(FlushModeType.COMMIT)
// 커밋이나 쿼리를 실행할 때 플러시 (기본값)
FlushModeType.AUTO
// 커밋할 때만 플러시 -> 쓸 일 없음.
FlushModeType.COMMIT
## 준영속 상태
- 영속 상태에서 분리(detach)된 상태로, 영속성 컨텍스트가 제공하는 위 기능을 모두 사용하지 못 한다.
- em.detach(entity)
- 특정 엔티티만 준영속 상태로 전환한다.
- em.clear()
- 영속성 컨텍스트를 완전히 초기화 한다.
- em.close()
- 영속성 컨텍스트를 종료한다.
- em.detach(entity)
// 영속 상태
Member member = em.find(Member.class, 150L);
// Dirty checking
member.setName("ZZZZZAAAA");
// 준영속 상태(이제 JPA와 관련없게 됨)
em.detach(member);
tx.commit();
- 이렇게 준영속 상태로 만들게 되면 JPA와 관련이 없으므로 update query가 나가지 않는다.