Persistent
JPA는 이름에서 알 수 있듯이 'Persistent' 라는 개념위에서 동작하게 된다.
위의 그림을 보면 애플리케이션에 유일하게 존재하는 Entity Manager Factory에서
Request들을 처리하기 위해 Entity Manager를 내어준다.
각 Request들은 DB를 사용하게 되는데, 이때 Entity Manager가 갖고 있는
Persistent를 통해 사용하게 된다
EntityManager.persist(member); // DB에 Member Entity 저장 예시
주석으로 DB에 저장한다고 써놓았지만, 사실 DB에 바로 저장이 되는것이 아닌
Entity Manager가 관리하는 Persistent에 저장된다.
Entity의 생명주기
Persistent에 저장되는 Entity들은 다음과 같은 State를 가진다
- 비영속 (new / transient) : JPA랑 아무 상관이 없는 상태
- new : 순수 자바 객체가 생성된 상태, persistent와 연결되어 있지 않음
- transient : 프로그래머가 임의로 persistent에 연결하지 않을것임을 명시
- 영속 (managed) : JPA와 연결이 되어있는 상태
- persist : 객체를 new로 생성한 이후, EntityManager.persist(member)한 상태
- 준영속 (detached) : JPA와 연결이 됐었다가 떨어진 상태
- ex) find를 통해 DB에서 객체를 가져온 다음 detach를 해주지 않고 객체의 필드를 수정하면
수정된 객체가 다시 DB에 자동으로 Update 되는데,
detach를 해주게 되면 JPA랑 아예 관계없는 순수 Java 객체로 변하여 자동으로 Update되지 않음
- ex) find를 통해 DB에서 객체를 가져온 다음 detach를 해주지 않고 객체의 필드를 수정하면
Persistent Context의 이점
- 1차 캐시와 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
1차 캐시와 동일성 보장
// 엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("홍길동");
// 엔티티를 영속
EntityManager.persist(member);
위의 코드를 실행시킨다음 EntityManager의 상태는 다음 그림과 같다
만약 위의 코드에서 DB에 commit을 하지 않았다면, DB는 현재 아무런 데이터를 가지고 있지 않다.
단지 EntityManager가 관리하는 persist에 데이터가 저장될 뿐이다.
EntityManager.find(Member.class, "member1");
위의 코드가 실행되면 EntityManager는 DB에 바로 접근해 Select Query를 날리는 것이 아닌
자신이 관리하는 persist에 member1이 있는지를 우선적으로 확인한다
이렇게 마치 캐시와 비슷하게 작동한다는것을 알 수 있다. => 성능 향상에 조금이라도 도움 되겠지?
하지만 EntityManager는 트랜잭션마다 생성되고 공유되지 않으므로(일회성), 크게 이점은 없다.
애플리케이션 전체에 공유되는 캐시는 2차 캐시로 불리며 이는 나중에 다룬다.
만약 Persist에 없는 멤버를 조회하면 어떻게 될까?
EntityManager.find(Member.class, "member2");
캐시라고 불리는 만큼 기대에 맞게 알아서 똑똑하게 일을 해준다
같은 트랜잭션 내에서는 당연히 같은 Entity Manager를 참조하며, 나아가 같은 Persist를 사용하므로
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
위와 같은 코드는 true를 뱉을것이다.
트랜잭션을 지원하는 쓰기 지연
transaction.begin(); // 데이터 변경을 알리는 트랜잭션 시작
EntityManager.persist(memberA);
EntityManager.persist(memberB);
// 현재까지 DB에 INSERT Query를 보내지 않음
transaction.commmit(); // 트랜잭션 커밋
// DB에 INSERT Query 보냄
이러한 쓰기 지연을 어떻게 가능하게 해줄까?
답은 아래와 같이 Entity Manager는 쓰기 지연을 위한 저장소를 따로 가진다.
저장소에 차곡차곡 쌓아두다가 트랜잭션의 commit이 끝나게 되면 flush가 발생되어 실제 DB에 INSERT Query를 날린다
변경 감지(Dirty Checking)
JPA가 관리하는 객체가 만약 변경이 되었다면 따로 Update하라는 코드 없이도
자동으로 JPA가 알아서 Update를 반영해준다. => 잘못된 예시 : EntityManager.update(member);
이때 Update도 Create와 마찬가지로 내부에 저장소를 두어 UPDATE Query를 쌓아 두었다가
commit이나 flush가 발생하게 되면 한꺼번에(Batch 방식으로) 처리한다
FLUSH
아니 Flush가 도대체 뭐길래?
대충 git commit과 비슷하게 생각하면 된다.
JPA가 관리하는 Entity들의 변경사항(Create, Update, Delete, ...)등을 실제 DB에 적용해주는 것이다.
Flush를 호출하는 방법은 다음과 같다
- EntityManager.flush() => 직접 호출
- transaction.commit() => flush가 호출됨
- JPQL query 호출 => flush가 되어야 제대로 된 JPQL query 결과를 받아 볼 수 있으므로
Flush는 Persist의 내용을 비우는것이 아닌, DB에 반영해주는것이다.
출처 : https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런
www.inflearn.com
'Back-End > JPA' 카테고리의 다른 글
[JPA] 6. 프록시와 Cascade 및 고아 객체 (0) | 2022.08.04 |
---|---|
[JPA] 5. 상속관계 Mapping & Mapped Superclass (0) | 2022.08.03 |
[JPA] 4. Entity간의 연관관계 Mapping (0) | 2022.07.30 |
[JPA] 3. Entity Mapping (0) | 2022.07.21 |
[JPA] 1. JPA에 대한 이해 (0) | 2022.07.20 |