영속성 컨텍스트란?
- JPA를 이해하는데 가장 중요한 용어
- 엔티티를 영구 저장하는 환경
EntityManager.persistece(entitiy);
엔티티를 영속성 컨텍스트에 저장한다.- 영속성 컨텍스트란 논리적인 개념으로 눈에 보이지 않는다.
- 엔티티 메니저를 통해 접근가능
엔티티의 생명주기
- 비영속
영속성 컨텍스트와 전혀 관계없는 새로운 상태
그냥 객체를 만든 것
1
2
3
Member member new Member();
member.setId(101L);
member.setName("hello");
- 영속
EntityManager.persistece(entitiy);
를 하여
영속성 컨텍스트에 관리되는 상태
1
2
3
4
5
6
7
8
9
10
//비영속
Member member new Member();
member.setId(101L);
member.setName("hello");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//영속
em.persist(member);
참고로 EntityManager.persistece(entitiy);
를 한다고 바로 DB에 저장되는건 아니다.
em.getTransaction().commit();
을 해야지 DB에 저장이 된다.
- 준영속
준영속은 해당 엔티티를 영속성 컨텍스트에서 분리시키는 것이다.
이를 준영속 상태라고 말한다.
1
2
3
4
5
em.detach(member); //특정 Entity만 준영속 상태로 전환
em.clear(); //영속성 컨텍스트를 완전히 초기화
em.close(); //영속성 컨텍스트를 종료
- 삭제
DB에서도 데이터를 날린다.
1
em.remove(member);
영속성 컨텍스트의 이점
- 1차 캐시
영속성 컨텍스트에는 캐시 기능이 있다.
만일 아래 코드에서처럼 persist
한 값을 조회한다면
DB에서 해당 데이터를 불러오는 것이 아니라 영속성 컨텍스트에서 불러온다.
1
2
3
4
5
6
7
8
9
10
11
12
13
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member = new Member();
member.setId(101L);
member.setName("hello");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "101L");
만일 Id가 102번인 값을 조회한다면
그 때는 영속성 컨텍스트가 아닌 DB에서 값을 찾는다.
- 동일성 보장
같은 값을 가져온다면 ==
비교에서도 true를 보장한다.
1
2
3
4
5
6
7
8
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member a = em.find(Member.class, "101L");
Member b = em.find(Member.class, "101L");
System.out.println(a == b);
//결과 : true
- 트랜잭션을 지원하는 쓰기 지원
커밋을 하기 전까지 새로 만들어진 데이터를
영속성 컨텍스트 안에 쓰기지연 저장소에 저장하고 있다가
커밋을 했을 때 DB로 정보가 간다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member1 = new Member();
member1.setId(101L);
member1.setName("hello");
Member member2 = new Member();
member2.setId(102L);
member2.setName("hello2");
em.persist(member1);
em.persist(member2);
//여기까지 DB로 쿼리를 날리지 않는다
//commit하는 순간 DB로 쿼리가 날라간다
tx.commit();
- 변경 감지(Dirty Checking)
만약 어떠한 데이터를 불러와서 해당 데이터의 이름을 변경했다고 하자.
데이터를 불러오는 순간
영속성 컨텍스트에는 1차 캐시가 존재하고
그 안에 스냅샷이라는 부분에 최초로 불러온 데이터가 저장된다.
그리고 데이터의 이름을 변경했고
커밋을 하는 시점에 스냅샷과 Entity의 데이터를 비교한다.
만약 데이터가 다르다면 update쿼리를 쓰기지연 저장소에 저장한다.
그리고 DB에 데이터를 반영하고 커밋을 하게된다.
그렇기 때문에 em.persist(findMember);
를 하지 않아도 되는 것이다.
1
2
3
4
5
6
7
8
9
10
11
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member findMember = em.find(Member.class, "101L");
//데이터를 찾아와서 이름을 새로운 값으로 변경
findMember.setName("hi");
//em.persist(findMember); 해야하나??
tx.commit();
플러시(flush)
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
- 영속성 컨텍스트를 비우지 않는다.
- 커밋 직전에만 데이터를 동기화하면 문제가 발생하지 않는다.
플러시의 과정
- 데이터의 변경 감지(Dirty Check)
- 수정된 Entity를 쓰기 지연 저장소에 등록
- 쓰기 지연 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제)
직접 호출 - em.flush()
미리 쿼리를 보고싶거나 DB에 미리 데이터를 반영하고 싶다면
flush()
함수를 사용해서 미리 쿼리를 날릴 수 있다.
1
2
3
4
5
6
7
8
9
10
11
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member = new Member(200L, "member200");
em.persist(member);
em.flush();
//커밋을 하기 전에 SQL 쿼리가 날라간다
tx.commit();
자동 호출 - 트랜잭션 커밋시
트랜잭션을 커밋하게 되면
커밋 직전 플러시가 실행되어 DB에 데이터가 반영된다.
자동 호출 - JPQL 쿼리 실행시
데이터를 여러개 받아와 JPQL 쿼리로 데이터를 불러오려고 한다.
만약 JPQL 쿼리 실행 전에 플러시가 실행되지 않는다면
JPQL 쿼리가 정상적으로 작동하지 않을 것이다.
이러한 상황을 방지하고자 JPQL 쿼리를 만나게되면
플러시가 실행된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member = new Member(200L, "member200");
em.persist(member1);
em.persist(member2);
em.persist(member3);
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
//JPQL 쿼리를 만나면 플러시가 실행된다