[JPA 긴급학습(6)] 엔티티 매니저와 영속성 관리
Entity Manager Factory
: Entity Manager를 만든다.
EntityManagerFactory emf
= Persistence.createEntityManagerFactory("~~");
엔티티 매니저 팩토리를 생성하는 코드.
생성비용이 큼.
따라서 어플리케이션 전체에서 공유하도록 설계되어 있다.
여러 쓰레드가 동시에 접근해도 안전하다.
그런데 Entity Manager는 여러 쓰레드가 동시에 접근하면 동시성 이슈가 존재한다. 따라서 쓰레드 간에 절대 공유하면 안된다.
Entity Manager
Entity Manager는 DB 커넥션을 연결이 꼭 필요한 시점까지 커넥션을 얻지 않는다.
persistence Context
Entity를 영구 저장하는 환경을 의미.
Entity Manager로 Entity를 저장하거나 조회하면 Entity Manager는 영속성 컨텍스트에 Entity를 보관하고 관리한다.
em.persist(member);
persist() 메소드는 Entity Manager를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장한다.
Persistnece Context(영속성 컨텍스트)는 논리적인 개념이다.
눈에 보이지도 않는다.
Persistnece Context는 Entity Manager를 생성할 때 하나 만들어진다.
그리고 Entity Manager를 통해 Persistnece Context에 접근할 수 있고, 관리할 수 있다.
Entity Lifecycle
(1) new/transient (비영속)
Persistenc Context와 전혀 관련없는 상태
Member member = new Member();
member.setId("member1");
member.setUsername("hong"):
단순히 Entity 객체를 생성한 상태.
순수한 객체상태이며 아직 저장하지 않았다.
따라서 어떤 Persistence Context나 DB와는 전혀 관련이 없다.
(2) managed (영속)
: Persistence Context에 저장된 상태
Entity Manager를 통해서 Entity를 Persistence Context에 저장했을 때.
Persistence Context가 관리하는 Entity를 통칭함.
결국 영속상태라는건 Persistence Context에 의해 관리되는 상태를 의미.
em.persist(member);
(3) detached (준영속)
: Persistence Context에 저장되었다가 분리된 상태
Persistence Context가 관리하던 managed 상태의 Entity를 Persistence Context가 관리하지 않으면 Detached(준영속)상태가 됨.
아래와 같은 코드를 실행하면 Detached상태가 된다.
em.detach(member);
(4) removed (삭제)
: 삭제된 상태
Entity를 Persistence Context와 DB에서 삭제한다.
em.remove(member);
Persistence Context의 특징
(1) Persistence Context와 식별자 값
Persistence Context는 Entity를 식별자 값(@Id가 붙은 인스턴스)로 구분한다. 따라서 Managed 상태는 식별자 값이 항상 존재해야한다
식별자가 값이 없으면 예외가 발생한다.
(2) Persistence Context와 DB 저장
Persistence Context에 entity를 저장하면 이 Entity는 언제 DB에 저장되는가
JPA는 보통 트랜잭션을 커밋하는 순간 Persistence Context에 새로 저장된 Entity를 DB에 반영하는데 이것을 Flush라고 한다.
(3) Persistence Context가 Entity를 관리함으로써 다음과 같은 장점을 얻는다.
1차 캐시
동일성 보장
쓰기 지연
변경 감지
지연 로딩
Entity 조회
(1) 1차 캐시에서 조회
Persistence Context는 내부에 캐시를 갖고 있다. 이것을 1차 캐시라고 한다. managed 상태의 Entity는 모두 이곳에 저장된다.
쉽게 말해서 내부에 Map이 있는데 키는 @Id로 매핑한 식별자고, 값은 Entity Instance다.
//member 객체 생성 후
em.persist(member);
이 코드를 실행하면 1차 캐시에 회원 Entity를 저장한다. 회원 Entity는 아직 DB에 저장된 것은 아니다.
1차 캐시의 키는 식별자 값이다. 그리고 식별자 값은 DB 기본키와 매핑되어 있다. 따라서 Persistence Context에 데이터를 저장하고 조회하는 모든 기준은 DB의 기본키 값이다.
Member member = em.find(Member.class, "member1");
첫번째 파라미터는 Entity 클래스 타입이고, 두번째 파라미터는 조회할 Entity의 식별자 값이다.
em.find()를 호출하면 먼저 1차 캐시에서 Entity를 찾고, 없으면 DB에서 조회한다.
DB에서 조회
만약 em.find()를 호출했는데 엔티티가 1차 캐시에 없으면 entity Manager는 DB를 조회해서 Entity를 가져온다. 그리고 가져온 Entity는 1차 캐시에 저장하고(Managed 상태 돌입) 그리고 Entity를 반환한다.
성능 상 이점을 누릴 수 있다.
동일성 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
a랑 b가 같다고 볼 수 있을까?
둘이 같은 인스턴스를 반환한다.
Persistence Context는 엔티티의 동일성 또한 보장한다.
Entity 등록
EntityManger em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(memberA);
em.persist(memberB); // 여기는 INSERT SQL을 안 보낸다.
transaction.commit(); // 여기서 INSERT SQL을 보낸다.
Entity Manager는 트랜잭션을 커밋하기 직전까지 DB에 Entity를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 그리고 트랜잭션을 커밋할 때 모아둔 쿼리를 DB에 보내는데 이것을 트랜잭션을 지원하는 쓰기지연 이라고 한다.
다시 정리해보면, EntityManger가 관리하는 Persistence Context에서 쿼리문들을 저장해놓는다. 그리고 commit()되는 순간 한번에 모아뒀던 쿼리문을 flush 한다. flush는 Persistence Context의 변경 내용을 DB에 동기화하는 작업이다. 이때 등록 / 수정 / 삭제의 Entity를 DB에 반영한다.
쓰기 지연이 가능한 이유
begin();
save(A);
save(B);
save(C);
commit();
각 save할 떄마다 DB에 등록 쿼리를 보내든 3개 save를 마치고 한번에 등록쿼리를 모두 보내든 결국 commit 시점은 같기 때문에 결과도 같다.
따라서 쓰기 지연이 가능하게 된다.
Enity 수정
SQL 수정 쿼리의 문제점
SQL을 사용하면 수정 쿼리를 직접 작성해야한다.
그런데 비즈니스 로직이 변경됨에 따라서 수정 쿼리가 무작정 늘어나게 된다.
만약 한번에 수정하는 쿼리라면 또 인자들을 입력하지 않는 실수 가능성도 늘어난다.
이러한 개발방식의 문제점은 수정 쿼리가 늘어나는 것은 물론이고, 비즈니스 로직을 분석하기 위해 SQL을 계속 확인해야 한다. 결국 직/간접적으로 비즈니스 로직이 SQL에 상당부분 의존하게 된다.
비즈니스 로직변경 <-> SQL 쿼리문 변경 둘이 엉켜있게 된다는거
변경 감지
JPA는 엔티티를 어떻게 수정하는가?
EntityManager em = emf.createEntityManger();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
Member memberA = em.find(Member.class, "memberA");
memberA.setUsername("hi");
memberA.setAge(10);
// em.update(member); // 이런 코드가 있어야 하지 않을까?
transaction.commit();
JPA로 Entity를 수정할 때는 단순히 Entity를 조회해서 데이터만 변경하면 된다.
트랜잭션 커밋 직전에 주석으로 처리된 em.update() 메소드를 실행해야 할 것 같지만 이런 메소드는 존재하지 않는다. Entity의 데이터만 변경했는데 어떻게 DB에 반영되는가? 이렇게 entity의 변경사항을 DB에 자동으로 반영하는 기능을 변경 감지(Dirty Checking)라 한다.
JPA는 Entity를 Persistence Contextdp 보관할 때, 최초 상태를 복사해서 저장해두는데 이거를 스냅샷이라 한다. 그리고 Flush 시점에 스냅샷과 Entity르 비교해서 변경된 Entity를 찾는다.
(1) 트랜잭션 커밋! -> Entity Manager 내부에서 먼저 Flush 호출 !
(2) Entity랑 스냅샷이랑 비교 -> 변경된 Entity를 찾는다 !
(3) 변경된 Entity를 찾았으면 수정 쿼리를 생성한다. 쓰기 지연 저장소에 보낸다.
(4) 쓰기 지연 저장소의 SQL을 DB로 보낸다.
(5) DB 트랜잭션을 커밋한다.
Dirty Checking은 Persistence Context가 관리하는 Managed 상태의 Entity에만 적용된다.
즉, 비영속 / 준영속처럼 Persistence Context의 관리를 받지 못하는 Entity는 값을 변경해도 DB에 반영되지 않는다.
JPA의 기본 strategy는 모든 Entity의 모든 Field를 업데이트한다
-
모든 필드를 사용하면 수정쿼리가 항상 같다.
따라서 어플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 모든 필드를 업데이트 할 수 있다. -
DB에 동일한 쿼리를 보내면 DB는 이전에 한 번 파싱된 쿼리를 재사용할 수 있다. 만약 필드가 많거나 저장되는 내용이 너무 크면 수정된 데이터만 사용해서 동적으로 UPDATE SQL을 생성하는 전략을 선택하면 된다. 이때는 하이버네이트 확장 기능을 사용해야한다.
@Entity @org.hibernate.annotations.DynamicUpdate @Table(name="Member") public class Member { .. }
이런 식.
해당 DynamicUpdate 어노테이션을 사용하면 수정된 데이터만 사용해서 동적으로 UPDATE SQL을 생성한다.
추가로 INSERT 시에도 데이터가 존재하는 필드만으로 INSERT SQL을 동적으로 생성하는 @DynamicInsert도 있다.
(칼럼이 대략 30개 이상이 되면 @DynamicUpdate가 더 빠르다고 한다)
Entity 삭제
Entity를 삭제하려면 먼저 삭제 대상 Entity를 조회해야 한다.
Member memberA = em.find(Member.class, "memberA");
em.remove(memberA);
em.remove()에 삭제 대상 Entity를 넘겨주면 Entity를 삭제한다. 물론 Entity를 즉시 삭제하는 것이 아니라 Entity 등록과 비슷하게 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록한다. 이후 트랜잭션을 커밋해서 Flush를 호출하면 실제 DB에 삭제 쿼리를 전다한다.
em.remove()를 호출하는 순간 memberA는 Persistence Context에서 제거된다. 이렇게 삭제된 Entity는 재사용하지 말고 자연스럽게 GC의 대상이 되도록 남겨두는 것이 좋다.
Flush
flush는 Persistence Context의 변경 내용을 DB에 반영한다. flush를 실행하면 구체적으로 다음과 같은 일이 일어난다.
1. Dirty Checking이 동작해서 Persistence Context에 있는 모든 Entity를 스냅샷과 비교해서 수정된 Entity를 찾는다. 수정된 Entity는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다.
2. 쓰기지연SQL저장소의 쿼리를 DB에 전송한다.(등록/수정/삭제 쿼리)
Persistence Context를 flush하는 경우는 3가지
-
em.flush()를 직접 호출.
-
트랜잭션 커밋 시 flush가 자동 호출
-
JPQL 쿼리 실행 시 flush가 자동 호출
Flush라는 이름 때문에 Persistence Context에서 Entity를 지운다고 생각하면 안된다.
Persistence Context의 변경 내용을 DB에 동기화하는 것을 의미한다
Detach 상태 (준영속)
Persistence Context가 관리하는 Managed 상태의 Entity가 Persistence Context에서 분리된 것을 의미.
Detatch 상태의 Entity는 Persistence Context가 제공하는 기능을 사용할 수 없다.
Managed 상태의 Entity를 Detach 상태로 만드는 방법은 크게 3가지다.
1. em.detach(entity) : 특정 entity만 detach상태로 전환
2. em.clear() : Persistence Context를 완전히 초기화
3. em.close() : Persistence Context를 종료.
em.detach(member);
em.clear();
위에 메소드들을 호출하면 1차 캐시부터 쓰기지연 SQL 저장소 까지 해당 Entity를 관리하기 위한 모든 정보가 제거된다.
병합 merge()
Detach(준영속) 상태의 Entity를 다시 Managed(영속) 상태로 변경하려면 병합을 사용하면 된다. merge() 메소드는 Detach 상태의 Entity를 받아서 그 정보로 새로운 Managed 상태의 Entity를 반환한다.
Author And Source
이 문제에 관하여([JPA 긴급학습(6)] 엔티티 매니저와 영속성 관리), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@red_gunny/JPA-긴급학습-엔티티-매니저와-영속성-관리저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)