우선 Member객체가 1차 캐시에 있다고 가정하자. 1차 캐시에는 'key'가 DB와 매핑한 P.K가 되고, 'value'로는 Entity객체(Member)가 온다.
//엔티티를 생성만하고 영속하지는 않은 비영속 상태
Member member1=new Member();
member1.setId("member1");
member1.setName("memberName1");
//영속 상태
em.persist(member1);
이때 내가 방금 영속 상태로 만든 member1 객체를 조회하고 싶다면, jpa는 바로 DB에서 조회하는게 아니라 1차 캐시에서 조회를 시도한다.
em.find(Member.class,member1); 로 member1객체를 찾게 되면, member1은 바로 1차 캐시에서 조회가 가능하다. 왜냐하면 내가 member1객체를 영속 상태로 만들어버렸기 때문이다.
만약 여기서 1차 캐시에 존재하지 않은(내가 영속 상태로 만들지 않은) 객체 member2를 조회하고 싶어졌다.
하지만 member2는 1차 캐시에 존재하지 않기 때문에 위에서 member1처럼 바로 1차 캐시에서 조회하는 것이 불가능하다.
그래서 우선 em.find(Member.class,member2); 를 하게 되면 DB에서 조회를 하게 된다(DB에 member2가 있다는 가정하에).
결론적으로 member2를 조회 하게 되면, 이 DB에서 조회한 member2는 1차적으로 1차 캐시에 저장되고 그 다음 이제서야 member2를 반환한다.
이후에 member2를 다시 조회 한다면, 이때는 1차 캐시에 있는 member2가 조회된다.
참고
EntityManager는 DB Transaction 단위로 만들고 DB Transaction이 끝나면 바로 종료가 된다. 즉, 고객의 요청이 하나 들어와서 비즈니스 로직이 끝나 버리면
영속성 컨텍스트를 지우게 되다는 것이다. 1차 캐시도 다 날라간다. 그래서 이 짧은 시간에서만 1차캐시에서 조회하는 이 매커니즘이 이득이 있기 때문에, 여러 명의 고객이 사용하는
캐시와는 거리가 멀다. 애플리케이션 전체에서 공유하는 캐시는 2차 캐시라고 한다. 1차 캐시에는 Transaction안에서만 효과가 있기 때문에 성능 이점을 얻을 수 있는 장점이 많지는 않다.
em.persist(member);를 하고 이 1차 캐시에 존재하는 member를 다시 find(조회)할 때 DB에 select쿼리가 나가지 않는다.(이건 뭐 당연한 사실)
Member findmember1 = entityManager.find(Member.class, 101L);
Member findmember2 = entityManager.find(Member.class, 101L);
System.out.println(findmember1 == findmember2); //TRUE
같은 Transaction안에서 위의 코드 처럼 같은 것을 조회했을때 == 비교를 하면 True가 나옴.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 Transaction을 시작해야 한다.
transaction.begin(); // Transaction 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 insert 쿼리문을 DB에 보내지 않는다.
//커밋하는 순간 데이터베이스에 insert 쿼리를 보냄.
transaction.commit(); // Transaction 커밋
//p.k 200에 해당하는 Entity는 현재 memberA라고 가정.
Member member = entityManager.find(Member.class, 200L);
member.setName("changedmemberA");
//entityManager.persist(member);
//다시 persist할 필요가 없음
jpa를 자바 컬렉션 처럼 생각하면 편하다. 내가 자바 컬렉션을 사용할 때 특정 값을 변경하게 되면 그 변경한 값을
다시 컬렉션에 저장하는 과정을 거치지 않는다.
jpa에서도 마찬가지... 이미 내가 persist했던 member객체인데 그 member객체의 이름 필드를 변경했다고 해서 이 변경한 값의 객체인 member를
다시 persist할 필요가 있을까? 느낌상 값을 일단 변경했으니까 이 변경한 내용을 DB에 반영하기 위해서는 update 쿼리를 날려야 할 것 같은데,,,,,?
--> 그럴 필요가 없이 DB에 값이 변경이 된다. 왜??????????
엔티티 삭제도 위에서 본 변경 감지의 매커니즘과 비슷하게 이번엔 delete 쿼리가 똑같이 동작한다.
플러시가 발생하면 1. 변경 감지(Dirty Checking) , 2. 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록 , 3. 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송(등록,수정,삭제 쿼리)
이 3가지의 과정을 거치게 된다.
플러시가 발생한다고 해서 DB의 Transaction이 commit되는 것은 아니다. 그러면 영속성 컨텍스트를 어떻게 플러시할까?
em.flush()를 직접 호출하여 플러시 한다.
Transaction을 commit하여 플러시를 자동적으로 호출하게 한다.
JPQL 쿼리를 실행하여 플러시를 자동적으로 호출하게 한다.
em.persist(member1);
em.persist(member2);
em.persist(member3);
//JPQL 쿼리 실행
query= em.createQuery("select m from Member m" , Member.class);
List<Memeber> members = query.getResultList;
원래 알던 내용을 토대로 생각했을때, em.persist()를 호출하면 아직 DB에 insert 쿼리가 날라가기 전이다. 하지만 아래에 JPQL을 통해 select 쿼리를 호출하게 되면 아직 DB에 저장된 것이 없으니 불러올 값이 없을 것이다.
이 부분에서 문제가 생길 수 있기 때문에, jpa는 JPQL 쿼리를 실행했을시 자동으로 플러시를 발생시키고 그 다음 DB에 쿼리가 날라가게 해두었다. 그래서 meber A B C가 조회가 된다.
결론적으로 플러시는 영속성 컨텍스트를 비우지 않는다
영속성 컨텍스트의 변경 내용을 DB에 동기화하는게 플러시다.
Transaction이라는 작업 단위가 여기서 키 포인트이다. commit 직전에만 동기화를 하면 된다.
간략하게 정리하자면, em.persist(member)를 하면 이것은 영속 상태가 되는 것을 알고 있다.
그리고 맨 위에서 어떤 객체를 조회 하려 할 때 그 객체가 1차 캐시에 없다면, 나는 DB에서 조회를 하고 그것을 1차 캐시에 올린 후 반환을 하는 구조로 돌아가는 것을 알고 있다.
여기서 1차 캐시에 올라간 상태를 영속 상태(jpa가 관리하는 상태)라고 했다.
-> em.persist()를 하여도 영속 상태가 되지만, em.find()를 했을 때 1차 캐시에 없다면, DB에서 조회하여 1차 캐시에 저장하는 것도 영속 상태인 것이다.
그렇다면 본론을 돌아와 준영속 상태는 무엇일까???
준영속 상태란 영속성 컨텍스트에 의해 관리된 영속 상태인 Entity가 영속성 컨텍스트에서 분리(detached)된 상태를 말한다.
준영속 상태가 되면 영속성 컨텍스트가 제공하는 기능(변경 감지...등등)들을 사용하지 못하게 된다.
Member member = entityManager.find(Member.class, 200L);
System.out.println("-----------------------------------------");
member.setName("rerechangedmemberAA");
entityManager.detach(member);
//commit하는 시점에 DB에 insert 쿼리가 날라가게 된다.
transaction.commit(); //위에서 저장하고 commit을 한다.
[실행결과]
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
-----------------------------------------
Entity를 준영속 상태로 만들게 되면 Entity를 변경을 해도 변경 감지가 일어나지 않아 update 쿼리가 날라가지 않는다.
Member member = entityManager.find(Member.class, 200L);
System.out.println("-----------------------------------------");
member.setName("rerechangedmemberAA");
entityManager.clear();
//영속성 컨테스트를 초기화하고 다시 조회한다면?
Member member2 = entityManager.find(Member.class, 200L);
member2.setName("newMemberAA");
//이러면 update 쿼리가 날라간다.
transaction.commit();
[실행결과]
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
-----------------------------------------
Hibernate:
select
member0_.id as id1_0_0_,
member0_.name as name2_0_0_
from
Member member0_
where
member0_.id=?
Hibernate:
/* update
jpa.Member */ update
Member
set
name=?
where
id=?
[1] jpa에서 가장 중요하게 생각해야할 것
[2] Entity 생명주기 4가지도 공부했다.
[3] 영속성 컨텍스트의 이점
[4] 플러시
[5] 준영속 상태
좋은 글 잘봤습니다!! 백엔드 하신지는 얼마나 되셨는지요.