JPA에서 가장 중요한 2가지
엔티티 매니저 팩토리와 엔티티 매니저
엔티티 매니저 팩토리를 통해 고객의 요청이 들어올 때마다 엔티티 매니저가각각 생성이 되고 엔티티 매니저는 내부적으로 DB의 커넥션(conn)을 이용해DB를 사용하게 된다.
ex) member.persist
비영속(new/transient) : 최초의 멤버 객체를 생성한 상태, 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 멤버 객체를 생성을 했음. 엔티티 매니저에 아무것도 넣지 않은 상태
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
영속(managed) : 영속성 컨텍스트에 관리되는 상태
- 엔티티 매니저 안에 있는 영속성 컨텍스트가 들어있음.
멤버 객체를 생성한 다음에 엔티티 매니저를 얻어와서 persist를 통해 member객체를 집어넣으면 엔티티 매니저안에 있는 영속성 컨텍스트 안에 member 객체가 들어가면서 영속 상태가 됨.
영속 상태가 된다고 해서 DB에 바로 쿼리가 날라가는 것이 아님.
트랜잭션을 커밋하는 시점에 영속성 컨텍스트에 있는 애가 DB에 쿼리가 날라감
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
package hellojpa;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//code
EntityTransaction tx = em.getTransaction(); // 트랜잭션 얻을 수 있음
tx.begin(); // DB 트랜잭션 시작
try {
// Member Entity의 상태가 비영속, JPA와 관련 없음, DB에 들어가지도 않음.
Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");
// 엔티티 매니저 안에 들어 있는 영속성 컨텍스트를 통해서 member 객체가 관리됨, DB에 저장되지 않음.
System.out.println(" ==== BEFORE ===== " );
em.persist(member);
System.out.println(" ==== AFTER ===== " );
tx.commit(); // DB 커밋
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
==== BEFORE =====
==== AFTER =====
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
준영속(detached): 영속성 컨텍스트에 저장되었다가 분리
삭제(removed): 삭제
em.detach(member); // 영속성 컨텍스트를 지움, 아무관계가 없어짐
em.remove(member); // 실제 DB 삭제를 요청하는 상태, 실제 영구 저장한 DB에서 지우는 상태
애플리케이션과 DB 사이에 중간 계층으로 존재(버퍼링, 캐싱의 이점)
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있음
멤버 객체를 생성한 비영속 상태에서 member객체를 집어넣으면
@Id | @Entity |
---|---|
"member1" | member |
1차 캐시
Key가 DB PK로 매핑한 애가 되고 Entity 객체 자체가 값이 됨
Key: "member1" value: member(객체 자체가 값)
// 엔티티를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 엔티티를 영속
em.persist(member);
장점은 무엇이 있을까?
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 1차 캐시에 저장됨
em.persist(member);
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
em.find를 통해서 조회를 하면 JPA는 영속성 컨텍스트에서 find("member1")라고 찾으면 DB에서 찾는 게 아니고 먼저 1차 캐시에서 값을 찾는다. 만약 1차 캐시에 값이 존재하면 캐시에 있는 값을 조회해온다.
만약 1차 캐시에 없는 member2를 조회하면
1. find('member2') - 1차 캐시 조회했지만 없음
2. JPA는 DB를 조회
3. 만약 DB에 member2가 있으면 1차 캐시에 member2를 저장
4. 반환
5. 이후에 다시 member2를 조회하면 DB를 조회하지 않고 1차 캐시에 저장된 값을 반환 해줌.
But. 사실 크게 도움이 되지는 않음
엔티티 매니저는 DB 트랜잭션 단위로 많이 만들고 DB 트랜잭션이 끝날 때 같이 종료를 시킴. 고객의 요청이 하나 들어와서 비즈니스 로직이 하나 끝나게 되면 영속성 컨텍스트를 지운다는 의미(1차 캐시도 다 날라감). 여러명의 고객의 사용하는 캐시가 아님. 애플리케이션 전체에서 공유하는 캐시는 2차 캐시. DB의 트랜잭션 하나당 1차 캐시에서 처리하기 때문에 큰 이득은 없음.
Member findMember = em.find(Member.class, "member2");
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//code
EntityTransaction tx = em.getTransaction(); // 트랜잭션 얻을 수 있음
tx.begin(); // DB 트랜잭션 시작
try {
// Member Entity의 상태가 비영속, JPA와 관련 없음, DB에 들어가지도 않음.
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");
// 엔티티 매니저 안에 들어 있는 영속성 컨텍스트를 통해서 member 객체가 관리됨, DB에 저장되지 않음.
System.out.println(" ==== BEFORE ===== " );
em.persist(member);
System.out.println(" ==== AFTER ===== " );
Member findMember = em.find(Member.class, 101L);
// 여기서 중요한거 조회용 SQL이 나가는지 안나가는지
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());
tx.commit(); // DB 커밋
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
==== BEFORE =====
==== AFTER =====
findMember.id = 101
findMember.name = HelloJPA
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
조회를 했는데 SELECT 쿼리가 안나감 why? 저장을 할 때 1차캐시에 저장이 됨.그리고 똑같은 PK로 조회를 했기 때문에 DB에서 가져오는 게 아니라 1차 캐시에 있는 걸 먼저 조회.
이번에는 코드를 약간 수정하면 어떻게 되는지 보자.
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//code
EntityTransaction tx = em.getTransaction(); // 트랜잭션 얻을 수 있음
tx.begin(); // DB 트랜잭션 시작
try {
// 영속
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
tx.commit(); // DB 커밋
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
SQL문이 한번만 실행이 됨.
JPA가 가져올 때 1차 캐시에 값이 없어서 DB에서 값을 가져오기 위해 SQL문이 실행됨. 이후 영속성 컨텍스트 1차 캐시에 값을 저장함. 이후에 같은 값을 요청하니까 1차 캐시부터 조회하기 떄문에 1차 캐시에 저장된 값을 반환해주기 때문에 SQL문을 실행할 필요가 없음
성능의 이점보다 객체지향적으로 코드를 작성하는데 이점이 있음.
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
마치 자바 컬렉션에서 똑같은 레퍼런스 객체를 꺼내면 값이 동일하듯이
JPA가 영속 엔티티의 동일성을 보장 - 1차 캐시가 있기 때문에 가능, 같은 트랙잰션 내에서 실행
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println( a== b); // 동일성 비교 true
먼저 트랜잭션을 시작하고 persist로 member객체를 저장
여기까지 INSERT SQL을 DB에 보내지 않는다.
영속성 컨텍스트 안에는 쓰기 지연 SQL 저장소도 있음.
1. meberA를 영속성 컨텍스트에 persist하는 순간 1차 캐시에 값이 들어가면서 동시에 JPA가 Entity를 분석해서 Insert 쿼리를 생성해서 쓰기 지연 SQL 저장소라는 곳에 쌓아 둠.
2. meberB를 영속성 컨텍스트에 persist하는 순간 1차 캐시에 값이 들어가면서 동시에 JPA가 Entity를 분석해서 Insert 쿼리를 생성해서 쓰기 지연 SQL 저장소라는 곳에 쌓아 둠.
3. JPA가 SQL 저장소에 쭉죽 쌓고 있음.
4. 트랜잭션을 커밋하는 순간 쓰기 지연 SQL 저장소에 있던 쿼리문들이 flush가 되면서 DB에 쿼리문들이 날아간다.
5. 그리고 실제 DB에 커밋된다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//code
EntityTransaction tx = em.getTransaction(); // 트랜잭션 얻을 수 있음
tx.begin(); // DB 트랜잭션 시작
try {
// 영속
Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "B");
em.persist(member1);
em.persist(member2);
System.out.println(" ================== ");
tx.commit(); // DB 커밋
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
}
}
인서트 쿼리가 2번 나갔지만 밑에 있는 프린트 문이 먼저 출력되는 것을 알 수 있음. 왜 이렇게 할까? persist 할때마다 쿼리문을 날리면 DB를 최적화할수 있는 여지 자체가 없음. 옵션 하나로 성능을 좌지우지 할 수 있기 때문에 사용
==================
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
JPA는 더티체킹 - 변경 감지(Dirty Checking)라는 기능으로 엔티티를 변경할 수 있음. DB의 값이 변경됨 -
1. DB에 트랜잭션 커밋을 하는 순간 내부적으로 flush()를 호출한다.
2. 엔티티와 스냅샷을 비교한다.
@Id | Entity | 스냅샷 |
---|---|---|
memberA | memberA | memberA 스냅샷 |
memberB | memberB | memberB 스냅샷 |
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//code
EntityTransaction tx = em.getTransaction(); // 트랜잭션 얻을 수 있음
tx.begin(); // DB 트랜잭션 시작
try {
// 영속
Member member1 = em.find(Member.class, 150L);
member1.setName("ZZZZ");
System.out.println(" ================== ");
tx.commit(); // DB 커밋
} 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=?
//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, “memberA");
em.remove(memberA); //엔티티 삭제
영속성 컨텍스트의 변경내용을 데이터베이스에 반영
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();
em.setFlushMode(FlushModeType.COMMIT)
영속 -> 준영속
영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
영속성 컨텍스트가 제공하는 기능을 사용 못함
준영속 상태로 만드는 방법
em.detach(entity)
특정 엔티티만 준영속 상태로 전환
em.clear()
영속성 컨텍스트를 완전히 초기화
em.close()
영속성 컨텍스트를 종료