[JPA] 영속성 컨텍스트 2

이영재·2025년 1월 23일
0

Spring

목록 보기
12/15

이전에 em.persist()를 해도 바로 insert 쿼리가 나가지 않았다. 그럼 중간에 무슨일이 일어나는 걸까?

결론 부터 말하면 em.persist() 후에 1차 캐시에 저장된다.

1차 캐시

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");

다음 그림처럼 동작한다.

바로 저장하지 않고 1차 캐시에 저장하지? 어떤 성능적인 이점이 있을까?

1. 중복되는 쿼리 감소

member1은 이미 DB에 저장되어있는 상태

Member member = new Member();
member.setId("member2");
member.setUsername("회원2");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member Member1 = em.find(Member.class, "member1");
Member Member2 = em.find(Member.class, "member2");

  • 1차 캐시에 저장되어 있는 member는 DB에 select 쿼리가 아닌 1차 캐시에서 값을 꺼내온다
  • 1차 캐시에 저장되어 있지 않으면 DB를 조회하고 1차 캐쉬에 저장한 후 다시 1차 캐시에서 값을 가져온다.

정리하면, 1차 캐시에 저장된 엔티티는 동일 트랜잭션 내에서 반복 조회 시 데이터베이스에 접근하지 않고, 메모리에서 직접 반환한다.

-> 위 예제에서 SQL을 출력하면 2개의 데이터를 조회했지만 Member2에 관한 Select 쿼리 1개만 나간다.

2. 영속 엔티티의 동일성 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭 션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다.

3. 엔티티 등록시 트랜잭션을 지원하는 쓰기 지연

em.persist() 해도 바로 DB에 저장되지 않고 SQL을 쌓아 두었다가 트랜잭션 커밋이 일어날 때 한번에 SQL을 보낸다.

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작

em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋

em.persist(); 바로 다음 그림처럼 동작한다.

transaction.commit(); 이 일어나면

  • 쌓아 두었던 SQL이 flush 되면서 DB에 member 객체가 저장된다.
  • 이점 : 여러 INSERT, UPDATE, DELETE 작업을 트랜잭션 커밋 시점에 모아서 실행하므로, 데이터베이스와의 통신 횟수를 줄일 수 있다.
    • 효과: 네트워크 비용 절감, 데이터베이스 연결 풀 사용 효율성 증가, 데이터베이스 부하 감소 등

4. 엔티티 수정(변경 감지)

영속 엔티티 데이터 수정은 어떻게 할까?

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);

//em.update(member) 이런 코드가 있어야 하지 않을까?

transaction.commit(); // [트랜잭션] 커밋
  • em.update(member) 이런 메서드로 변경할 것 같지만 그렇지 않다.

다음 그림처럼 내가 최초로 읽어온 데이터를 스냅샷으로 기억하고 있다가 flush가 일어날 때 변경된 부분을 비교해 Update SQL을 만들어 변경한다.

다음 포스팅에서는 플러시와 준영속 상태에 대해 좀 더 자세히 알아보자

0개의 댓글

관련 채용 정보