해당 시리즈는 김영한님의 JPA 로드맵을 따라 학습하면서 내용을 정리하는 글입니다
JPA
를 이해하는데 가장 중요한 용어입니다EntityManager.persist(entity);
EntityManager
안에 영속성 컨텍스트가 있다고 생각하면 됩니다// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 객체를 저장한 상태(영속)
em.persist(member);
em.persist(member);
에서 바로 DB
로 쿼리가 날아가는게 아닙니다transaction
을 커밋하는 시점에 날아갑니다// 회원 엔티티를 영속성 컨텍스트에서 분리(준영속)
em.detach(member);
DB
에 삭제를 요청한 상태입니다// 객체를 삭제한 상태(삭제)
em.remove(member);
영속성 컨텍스트 내부에는 1차 캐시 공간이 있습니다
Map
의 구조로 DB
PK
를 키로 하고 객체를 값으로 가지고 있습니다
이렇게 1차 캐시를 가지면, 조회시에 캐시를 먼저 찾아보는 이점을 가질 수 있습니다
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 1차 캐시에 저장됨
em.persist(member);
// 1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
1차 캐시에 없는 객체를 조회하는 경우는 어떻게 될까요?
DB
에서 조회를 합니다DB
에서 조회한 객체를 1차 캐시에 저장한 후에 결과를 반환합니다물론 영속성 컨텍스트는 트랜잭션 단위로 생성되므로 사용후에 사라진다는 점에서 크게 캐싱 능력이 도움이 되지 않을 수도 있지만 규모가 크다면 의미 있는 성능 향상이 있을 수 있습니다
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // 동일성 비교 true
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.
tx.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
// 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다
// 커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다
transaction.commit(); // [트랜잭션] 커밋
memberA
를 저장하면 1차 캐시에 memberA
를 저장하면서 동시에 영속성 컨텍스트 내부에 있는 쓰기 지연 SQL 저장소
에 JPA
가 분석한 memberA
에 대한 INSERT
쿼리를 쌓아둡니다memberB
의 경우에도 같은 동작이 수행됩니다DB
에 날아가게 됩니다(flush
) 그 후 트랜잭션이 커밋됩니다EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.serUsername("hi");
memberA.serAge(10);
// em.update(member); 같은 코드가 필요해 보이지만 자동으로 감지해서 SQL문을 생성해준다
tx.commit();
JPA
의 목적이 데이터를 객체의 컬렉션을 다루듯이 사용하는 것처럼, 값을 변경하는 시점에서 작업이 끝나도록 기능이 만들어져 있습니다JPA
는 트랜잭션이 커밋되는 시점에 flush
가 호출됩니다. 이 때 JPA
는 1차 캐시안의 엔티티와 스냅샷(값을 읽어오는 시점의 엔티티의 최초 상태를 말합니다)을 비교합니다UPDATE
쿼리를 쓰기 지연 SQL 저장소
에 저장합니다DB
로 flush
되고 트랜잭션이 커밋됩니다// 삭제 대상 엔티티 조회
Member member = em.find(Member.class, "memberA");
em.remove(memberA); // 엔티티 삭제
DB
에서 데이터를 삭제합니다쓰기 지연 SQL 저장소
에 있는 쿼리들이 DB
에 반영이 되는 과정입니다쓰기 지연 SQL 저장소
에 등록합니다쓰기 지연 SQL 저장소
의 쿼리를 데이터베이스에 전송합니다(등록, 수정, 삭제 쿼리)em.flush()
- 직접 호출하는 방법
트랜잭션 커밋
- 플러시 자동 호출되는 방법
JPQL 쿼리 실행
- 플러시 자동 호출되는 방법
JPQL
쿼리 실행 시 플러시가 자동으로 호출되는 이유em.persist(memberA);
em.persist(memberA);
em.persist(memberA);
// 중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
persist
시점에는 쿼리가 날아가지 않으므로, JPQL
쿼리 실행 전에 자동으로 플러시되어 쿼리를 날려놔야 JPQL
쿼리가 의도에 맞게 동작할 것입니다em.setFlushMode(FlushModeType.COMMIT)
em.detach(entity)
: 특정 엔티티만 준영속 상태로 전환em.clear()
: 영속성 컨텍스트를 완전히 초기화em.close()
: 영속성 컨텍스트를 종료