JPA의 내부 동작은 기본적으로 엔티티 매니저가 각각의 트랜잭션 내에서 영속성 컨텍스트를 가짐으로써 동작한다.
♻️ 영속성 컨텍스트
엔티티 라이프 사이클
JPA의 영속성 컨텍스트란 “엔티티를 영구 저장하는 환경”을 말한다.
각각의 트랜잭션 내에서 엔티티 매니저는 주어진 영속성 컨텍스트에 접근하고, 해당 영속성 컨텍스트를 통해서 여러가지 장점을 발휘한다.
엔티티의 객체 생성 부터 영속성 컨텍스트에서 관리되고 DB에 저장되기 까지의 생명주기를 확인해보자.
가장 처음 객체가 생성되는 상태는 엔티티 매니저에서 관리하지 않는 상태이다. 이를 비영속 상태(new/transient) 라고 한다.
// 새롭게 객체가 생성된 상태 (New 상태) : 엔티티 매니저가 관리하고 있지 않다.
Member member = new Member();
member.setAge(28);
member.setName("Zayson");
이렇게 새롭게 생성된 객체를 영속성 컨텍스트에서 관리하기 위해서 엔티티 매니저는 persist() 메소드를 이용해 새롭게 생성된 객체를 영속 상태로 관리하거나 DB에서 엔티티 매니저가 find() 메소드를 이용해 객체를 조회해온다. 이를 영속 상태(Managed)라고 한다.
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("hello");
EntityManager entityManager = entityManagerFactory.createEntityManager(); // DB Connection을 받은 것과 동일하다고 생각하기.
entityManager.getTransaction().begin();
entityManager.persist(member); // 비영속 상태 객체 -> 영속 상태로 관리
entityManager.find(Member.class, "member"); // DB에서 객체 조회해오기.
영속 상태와 반대로 영속성 컨텍스트에서 관리하던 객체를 영속성 컨텍스트에서 분리하는 경우가 있는데 이를 준영속 상태(Detached)라고 한다. 준영속 상태는 엔티티 매니저가 detatch(), clear(), close() 메소드를 이용해 영속성 컨텍스트에서 분리한다.
// 영속 상태 -> 준영속 상태 , 더 이상 영속성 컨텍스트에서 관리하지 않음
entityManager.detach(member);
// 객체를 삭제
entityManager.remove(member);
객체를 삭제하는 경우 엔티티 매니저의 remove()를 이용해 객체를 삭제하며 이 경우 실제 delete query 날릴 때 DB에 삭제요청을 한다.
이러한 사이클을 엔티티 라이프 사이클 이라고 한다.
👍 영속성 컨텍스트의 이점
1차 캐싱
영속성 컨텍스트에서 관리하는 객체를 조회하는 경우에 DB에 접근해서 해당 객체를 조회하는 것이 아닌 영속성 컨텍스트 내 1차 캐시에서 데이터를 조회해온다.
물론 각각의 트랜잭션에서 엔티티 매니저가 영속성 컨텍스트를 부여받기 때문에 10개의 동일 요청이 들어온다 해서 하나의 영속성 컨텍스트의 1차캐시에 모두 접근하는 것이 아닌 각각의 1차 캐시에서 조회하기 때문에 성능적으로는 큰 장점을 갖진 않는다.
만약, 현재 영속성 컨텍스트에서 관리하고 있지 않은 객체를 조회하는 경우 DB에서 조회한 데이터를 영속성 컨텍스트 내 1차 캐시에 저장한 후 해당 객체를 반환한다.
Member member = new Member();
member.setId("member");
member.setAge(28);
member.setName("Zayson");
entityManager.persist(); // 영속 상태가 됨과 동시에 1차 캐시에 저장
// 1차 캐시에서 조회
Member findMember = entityManager.find(Member.class, "member");
영속 상태 엔티티의 동일성 보장
영속성 컨텍스트는 1차 캐시에서 객체 조회 시 트랜잭션 격리 수준을 Repeatable Read 등급을 애플리케이션 차원에서 지원하기 때문에 동일 트랜잭션 내 반복적인 데이터들은 동일한 객체로 판단한다.
즉, 1차 캐시에서 반복 조회해온 데이터는 객체이더라도 == 비교가 가능하다.
Member memberA = entityManager.find(Member.class, "member");
Member memberB = entityManager.find(Member.class, "member");
// memberA == memberB -> true
트랜잭션을 지원하는 쓰기 지연
persist는 객체를 영속성 컨텍스트에서 영속 상태로 관리하기 위한 메소드이지 DB에 insert 쿼리를 날리는 역할을 하는 것이 아니다.
persist를 실행하면 엔티티 매니저는 영속성 컨텍스트 내의 쓰기 지연 SQL 저장소에 insert SQL을 생성해 저장한다. 물론 1차 캐시에도 저장된다.
이러한 동작을 반복하다가, 엔티티 매니저가 commit을 하면 쓰기 지연 SQL 저장소에 있던 insert SQL이 일괄적으로 호출되며 데이터가 DB에 저장되며 동기화된다.
따라서, 반복적인 insert 쿼리를 호출하는 것이 아닌 commit 시점에 단 한번 호출을 통해 여러 데이터를 동기화 하는 것이 가능하다.
Member memberA = new Member(1L,"Zayson");
Member memberB = new Member(2L,"Zayson Maeng");
entityManager.persist(memberA); // 영속 상태 : 1차 캐시 및 쓰기지연 SQL 저장소에 저장 - DB insert 안된 상태
entityMaanger.persist(memberB);
entityManager.commit(); //commit 시점에 쓰기 지연 SQL 저장소에 있던 쿼리 일괄 실행 및 DB 동기화
변경 감지
영속성 컨텍스트에서 관리하는 객체는 update와 같은 메소드 없이 자바 컬렉션 수정하는 것과 비슷한 매커니즘으로 데이터를 변경하는 것이 가능하다.
즉, 엔티티 매니저는 영속 상태의 객체를 setter 메소드를 이용해 데이터를 변경하면 해당 데이터를 영속성 컨텍스트 내 1차 캐시의 변경 이전의 객체와 이후의 객체의 스냅샷을 비교한 후 Update 쿼리를 생성해 쓰기 지연 SQL 저장소에 저장한다. 이후 commit 시점에 DB에 update 쿼리를 실행한다.
Member memberA = entityManager.find(Member.class, "member"); // 영속 상태의 객체
memberA.setName("Woi Changed"); // 데이터 변경을 감지 -> 1차 캐시의 스냅샷 비교 , 쓰기 지연 SQL 저장소에 쿼리 저장
entityManager.commit(); // commit 시점에 동기화
✨ 플러시 (flush)
모든 Commit 시점 이전에 엔티티 매니저는 flush를 발생시킨다. flush는 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송한다.
즉, 트랜잭션이 Commit 될때 자동으로 flush가 발생된 후 commit이 된다.
flush는 엔티티 매니저를 통해 직접 호출이 가능하며, JPQL 쿼리를 실행 시에는 자동으로 호출된다.
entityManager.persist(memberA);
entityManager.persist(memberB);
entityManager.persist(memberC);
// 커밋이 안된 시점이므로 객체들이 영속 상태 및 SQL 저장소에 저장은 되어있지만 , DB 에는 반영되어있지 않다.
//JPQL 쿼리 실행 시에 자동으로 flush가 호출 되는 이유는 위의 객체들을 DB 동기화 후 사용하기 위해 자동 호출
entityManager.creatQuery("select m from Member as m", Member.class);
flush는 영속성 컨텍스트를 비우는 것이 아닌 영속성 컨텍스트에서 관리되는 객체의 데이터들을 DB에 동기화 하는 작업을 한다.
쓰기 지연 SQL 저장소에서 저장된 모든 쿼리를 flush를 통해 한번에 날리는 것이 가능한 이유는 영속성 컨텍스트 내에서 관리되는 것이 트랜잭션 작업단위 안에서 모두 이뤄지기 때문이다.
따라서, 데이터의 변경 내역을 DB에 커밋 직전에만 날려 동기화 해주면 된다.
JPA는 데이터 동시성 이슈를 트랜잭션에 위임해서 사용한다.
📄 References
김영한님의 자바 ORM 표준 JPA 프로그래밍 - 기본편 : https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
'Backend > JPA' 카테고리의 다른 글
다양한 연관관계 매핑 (0) | 2022.05.02 |
---|---|
단방향, 양방향 매핑 (0) | 2022.04.26 |
기본키 매핑, @Id, @GeneratedValue (0) | 2022.04.19 |
필드와 컬럼 매핑, @Column (0) | 2022.04.19 |
객체와 테이블 매핑, @Entity, @Table (0) | 2022.04.19 |