엔티티 매니저 팩토리 & 엔티티 매니저
영속성 컨텍스트에 대해 알아보기 전에 엔티티 매니저 팩토리와 엔티티 매니저에 대해서 알아보겠습니다.
엔티티 매니저 팩토리는 생성되는 시점에 DB 커넥션 풀을 생성해 둔 후, 고객의 요청이 들어올 때마다 엔티티 매니저를 생성합니다.
엔티티 매니저는 DB 연결이 필요한 시점(보통 트랜잭션이 시작되는 경우)에 커넥션 풀에 있는 connection을 얻습니다.
엔티티 매니저 팩토리가 엔티티 매니저를 사용하는 이유
엔티티 매니저 팩토리는 생성되는 시점에 DB 커넥션 풀을 생성하기에 생성 비용이 매우 큽니다.
그러나 엔티티 매니저의 생성 비용은 거의 들지 않으므로, 엔티티 매니저 팩토리는 필요에 따라 앤티티 매니저를 생성하여 사용합니다.
엔티티 매니저 팩토리는 스레드 세이프(Thread Safe)합니다.
즉 여러 스레드가 동시에 접근하여도 안전한 반면, 엔티티 매니저는 여러 스레드가 동시에 접근할 경우 동시성 문제가 발생하게 됩니다.
즉 엔티티 매니저는 절대로 공유해서는 안됩니다.
영속성 컨텍스트(Persistence Context)
엔티티(Entity)를 저장하고 관리하는 저장소의 개념입니다.
영속성 컨텍스트는 엔티티 매니저를 통해서 접근할 수 있습니다.
영속성 컨텍스트에 관리되는 엔티티의 상태를 영속 상태라 합니다.
영속성 컨텍스트의 특징
식별자 값
영속성 컨텍스트는 엔티티를 식별자 값(@Id 를 사용하여 테이블의 기본 키(PK)와 매핑한 값)으로 구분합니다.
따라서 영속 상태가 되기 위해서는 반드시 식별자가 존재해야 합니다.
영속성 컨텍스트가 제공해주는 기능
1. 1차 캐시
영속성 컨텍스트는 내부에 1차 캐시를 가지고 있습니다.
위의 그림을 보면 영속성 컨텍스트의 1차 캐시 속에 Member 객체가 1개 들어가 있습니다.
코드로 표현하면 다음과 같습니다.
//엔티티를 생성한 상태 (비영속)
Member member = new Member();
member.setId("member1");
member.serUsername("회원1");
//엔티티를 영속화, em은 EntityManager
em.persist(member);
(비영속은 말 그대로 영속화되지 않은 상태를 의미합니다)
위 코드를 실행하면 em.persist(member); 를 통해 member가 영속성 컨텍스트(1차 캐시)에 저장됩니다.
(영속성 컨텍스트를 1차 캐시라 이해해도 무방합니다)
이제 조회를 한번 해보겠습니다.
Member member = new Member();
member.setId("member1");
member.serUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
em.persist(member)를 통해 member를 1차 캐시에 저장해 준 후, 그 상태에서 조회를 하는 코드입니다.
조회를 하기 위해서는 DB에 연결해서, DB에 존재하는 Member 테이블의 값들 중 PK가 member1 인 값을 찾아오는 SQL을 수행해야 합니다.
그러나 1차 캐시 덕분에 DB에 SQL을 수행하지 않아도 member의 값을 가져올 수 있습니다.
조회 시 흐름
- "member1"조회 요청 시 JPA는 영속성 컨텍스트의 1차 캐시에서 해당 PK값을 가진 Member객체를 찾습니다.
- 1차 캐시에 존재한다면 그 값을 반환하고, 존재하지 않는다면 DB를 조회하여 값을 가져옵니다.
- DB를 조회하여 값을 가져온 경우라면, 가져온 값을 1차 캐시에 저장해둡니다.
- 다음번에 "member1"을 조회하면, DB를 거치지 않고 1차 캐시에서 가져올 수 있게됩니다.
그림으로 표현해보겠습니다.
(참고)
엔티티 매니저는 보통 데이터베이스 트랜잭션 단위로 생성하고, 트랜잭션이 끝나는 시점에 종료시킵니다.
엔티티 매니저가 종료되면 엔티티 매니저의 1차 캐시에 존재하는 데이터들도 모두 지워집니다.
트랜잭션은 길게 발생하지 않고, 굉장히 짧은 찰나의 순간에만 존재합니다.
따라서 1차캐시를 통해서 그렇게 큰 성능 이점을 얻기는 힘듭니다.
2. 영속 엔티티의 동일성 보장
동일성 비교: ==
동등성 비교: equals
다음 코드를 보며 설명하겠습니다.
//Id가 1인 Member 엔티티 조회
Member findMember1 = em.find(Member.class, 1L);
Member findMember2 = em.find(Member.class, 1L);
System.out.println(findMember1 == findMember2);//true가 반환
JPA는 하나의 트랜잭션 안에 존재하는(= 1차 캐시 내에 존재하는) Id(식별자 값)가 같은 엔티티에 대해서 동일성을 보장해줍니다.
즉 다음과 같이 표현 가능합니다.
1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다.
영속 엔티티와, 비영속 혹은 준영속 엔티티의 비교
동일성을 보장하지 않습니다.
코드를 통해 살펴보겠습니다.
Member memberA = em.find(Member.class, 1L);
em.clear();//해당 코드가 있으면 false, 없으면 true
Member memberB = em.find(Member.class, 1L);
System.out.println(memberA == memberB);
위 코드에서 em.clear()로 영속성 컨텍스트를 비워주면, false가 출력됩니다.
그렇다면 다음은 어떨까요?
Member memberA = em.find(Member.class, 1L);
Member memberB = em.find(Member.class, 1L);
em.clear();
System.out.println(memberA == memberB);
위 코드를 실행 결과는 true입니다.
이유는 다음과 같습니다.
첫 번째 find를 통해 DB에서 Member를 조회합니다. 그리고 조회한 멤버를 1차캐시에 등록합니다.
두 번째 find는 1차 캐시에서 값을 확인했는데 존재합니다. 따라서 1차 캐시에 존재하는 값을 가져옵니다.
그 이후 영속성 컨텍스트를 비우더라도, 이미 memberB는 1차 캐시에 존재하는 member의 참조주소를 가지고 왔기에 true가 출력되는 것입니다.
3. 엔티티 등록 시 쓰기 지연
코드를 통해 설명하겠습니다.
EntityTransaction transaction = em.getTransaction();
//엔티티 변경은 항상 트랜잭션 속에서 이루어져야 한다.
transaction.begin():
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 전송하지 않는다.
//트랜잭션을 커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다
transaction.commit();
영속성 컨텍스트에는 1차캐시 말고도 "쓰기 지연 저장소"가 존재합니다.
위 코드에서 em.persist(memberA);를 하면 INSERT SQL이 생성되어 "쓰기 지연 저장소"에 저장됩니다.
마찬가지로 em.persist(memberB);를 하면 INSERT SQL이 생성되어 "쓰기 지연 저장소"에 저장됩니다.
이렇게 계속 변경이 일어나면 SQL을 생성하여 쓰기 지연 저장소에 저장했다가, 트랜잭션이 커밋되는 순간 한번에 DB에 전송합니다.
옵션
spring.jpa.properties.hibernate.jdbc.batch_size=
위 옵션을 통해 한꺼번에 INSERT/UPDATE를 실행할 크기(쓰기 지연 저장소에 저장할 SQL의 최대 개수)를 지정할 수 있습니다.
4. 변경 감지(Dirty Checking)
EntityTransaction transaction = em.getTransaction();
transaction.begin()://트랜잭션 시작
//엔티티 조회 -> 영속화
Member memberA = em.find(Member.class, 1L);
memberA.setUsername("신동훈");
memberA.setAge(22);
transaction.commit();//트랜잭션 커밋
위 코드를 보시면 update 쿼리가 없습니다.
아마도 일반적으로는 코드가 다음과 같아야 할 것입니다.
EntityTransaction transaction = em.getTransaction();
transaction.begin();//트랜잭션 시작
//엔티티 조회 -> 영속화
Member memberA = em.find(Member.class, 1L);
memberA.setUsername("신동훈");
memberA.setAge(22);
em.update(memberA);//추가
transaction.commit();//트랜잭션 커밋
그러나 JPA 에서는 별도의 update 코드가 필요하지 않습니다.
JPA는 1차 캐시에 들어있는 엔티티가 변경된다면, 그 변경된 내용을 감지하여,트랜잭션 커밋 시점에 변경된 내용을 DB에 반영한다.
이것이 변경감지입니다.
변경 감지의 원리
1차 캐시에는 사실 @Id와 Entity 말고도 하나의 컬럼이 더 존재합니다.
바로 "스냅샷" 이라는 컬럼입니다.
엔티티가 1차 캐시에 저장될 때, 저장되는 시점의 상태를 스냅샷으로 만들어 1차 캐시에 보관합니다.
트랜잭션이 커밋되는 시점에 엔티티와 스냅샷을 비교하는데, 이때 만약 엔티티가 변경되었다면 당연히 스냅샷과 차이가 있을 것이고, JPA는 이를 감지하여 DB에 반영합니다.
중요한 것은 "커밋되는 시점"에 "엔티티와 스냅샷을 비교"한다는 것입니다.
따라서 커밋되기 전에 엔티티를 수정하여 사용하였다가, 커밋되는 시점에 다시 원상복구 시키면 update 쿼리가 실행되지 않습니다
코드를 통해 살펴보겠습니다.
EntityTransaction transaction = em.getTransaction();
transaction.begin();//트랜잭션 시작
//엔티티 조회 -> 영속화
Member memberA = em.find(Member.class, 1L);//이 시점에 username="신동훈", age=22
memberA.setUsername("바보");
System.out.println(memberA.getUsername());//바보 출력
memberA.setUsername("신동훈");
System.out.println(memberA.getUsername());//신동훈 출력
transaction.commit();//트랜잭션 커밋
위 코드를 실행하면 update 쿼리는 발생하지 않습니다.
즉 하나의 트랙잭션 내에서는 엔티티를 어떻게 수정하여 사용하건 상관없이, 트랜잭션이 커밋되는 시점의 엔티티의 상태와 스냅샷을 비교하여 반영된다는 것을 알고 넘어갔으면 좋겠습니다.
Reference
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., 본 강의는 자바 백엔
www.inflearn.com
1. 엔티티 (Entity) 와 엔티티 매니저 (Entity Manager)
엔티티 매니저 특정 작업을 위해 데이터베이스에 액세스하는 역할을 담당한다. 엔티티를 DB 에 등록/수정/삭제/조회(CRUD) 하는 역할이며, 엔티티와 관련된 일을 처리하는 엔티티 관리자이다. -
lhwn.tistory.com
#01 엔티티 매니저 팩토리와 엔티티 매니저
Index 사용방법과 주의사항 DB connection 획득 정리 JPA가 제공하는 기능은 크게 두가지가 있다. 1. 엔티티와 테이블을 매핑한다. 2. 설계 부분과 매핑한 엔티티를 실제 사용한다. EntityManager 는 Entity
debaeloper.tistory.com
3. [JPA] 영속성 컨텍스트란?
내가 처음 영속성 컨텍스트라는 단어를 접했을 때 해당 용어가 의미하는 바를 정확히 이해하지 못하였다. "영속성" + "컨텍스트" 두 단어의 조합으로 이루어진 용어인 것 같은데 단어 하나하나를
lng1982.tistory.com
MySQL 환경의 스프링부트에 하이버네이트 배치 설정 해보기 | 우아한형제들 기술블로그
{{item.name}} 안녕하세요. 배민상품시스템팀 권순규 입니다. 저희팀에서 하이버네이트 배치 설정을 통해 대량 insert/update 시의 속도개선을 경험하여 공유드리고자 합니다. 전체 예제 파일은 github
techblog.woowahan.com
'🏝️ Spring > JPA' 카테고리의 다른 글
[JPA] 필드와 컬럼 매핑 - @Enumerated (0) | 2021.12.14 |
---|---|
[JPA] 필드와 컬럼 매핑 - @Column (0) | 2021.12.14 |
[JPA] 엔티티 매핑 - @Entity, @Table (0) | 2021.12.13 |
[JPA] 플러시와 플러시 타입(FlushModeType) (0) | 2021.12.12 |
[JPA] 엔티티의 생명주기 - 비영속, 영속, 준영속, 삭제 (0) | 2021.12.12 |