[김영한 - JPA 기본] #3 내부 동작방식
JPA 에서 가장 중요한 것 ?
객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
영속성 컨텍스트 (Persiste Context)
EntityManger 와 Factory
EntityMangerFactory는 사용자 요청이 있을 때마다
EntityManger를 생성하여 커넥션풀에 접근해서 DB로부터 DML을 수행한다
영속성 컨텍스트 (Persiste Context)
엔티티를 영구 저장하는 환경
1차 캐시라고 생각해도 좋다
논리적인 개념
EntityManger를 통해 접근한다스프링같은 경우 사용자 요청마다 만들어진
EntityManger들이 하나의영속성 컨텍스트에 접근한다- 즉 N : 1 관계
엔티티 생명 주기
비영속 (new/transient)
persistence context에 속하지 않은 상태
영속 (managed)
persistence context에 속한 (관리되는) 상태
준영속 (detached)
persistence context에 속하였다가 분리된 상태
삭제 (removed)
- DB와 1차 캐시 모두에서 삭제된 상태
persistence context 의 이점
1차 캐시
- DB 읽기 전에 읽어옴
동일성(ientity) 보장
- 반복적 읽기 (
Reapeatable Read지원 ) - DB가 아닌 App 수준에서 지원
- 반복적 읽기 (
트랜잭션을 지원하는 쓰기 지연
commit()하기 전까지는 DB에 영향을 미치지 않는다
변경 감지 (Dirty Checking)
- update시 감지를 함
- 그렇다고 정말
update란 구문이 필요한 게 아닌 자바 구문의settter를 통해 감지함 - 스냅샷과의 비교를 통해서 변경되었음을 감지
commit()을 할때flush()가 호출되는데 이때 스냅샷과 Entity를 비교해서 Entity가 변경되었다면 update 쿼리 발생- 스냅샷: 1차 캐시에 처음 저장된 순간의 상태 (사진같은 느낌)
지연 로딩
- 다른 entity를 조회하는 순간에 읽어오는 기능이 있음 (예 member객체를 통한 team 조회)
플러시 flush
persistence context의 (변경 내용)을 DB에 반경
flush 가 호출이 될 때
- flush() 로 직접 호출
- 자동 호출
- 트랜잭션 커밋
- JPQL 쿼리 실행
- 실험해보았지만 실제로 호출되는지(동기화) 확인을 하지 못하였다
em.persist(new Member(101L, "user A"));
em.persist(new Member(102L, "user B"));
em.persist(new Member(103L, "user C"));
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
members.forEach(System.out::println);
flush 특징
persistence contextDB와 동기화동기화를 했다 하여 1차 캐시 (
persistence context) 를 비우지 않음트랜잭션이라는 작업 단위를 만들어준다!
flush 모드 옵션
- 건들 일도 없으며 수정하지 않는 것이 좋다
em.setFlushMode(FlushModeType.AUTO);
- 커밋이나 쿼리를 실행할 떄 (기본값)
em.setFlushMode(FlushModeType.COMMIT);
- 커밋할때만
준영속 상태
영속 상태에서 분리되는 상태
- 더티 체킹 등의 기능을 받지 못함
영속 상태에 등록되는 2가지
- persist()
- find()
- API
detach(오브젝트)특정 오브젝트 준영속화
- 예)
em.detach(member);
clear()모든 entity 준영속화
커스텀으로 만들어본 JPA Manager
실행부
PhilzManger philzManger = new PhilzManger();
philzManger.persist(new Member(1L, "user A"));
philzManger.persist(new Member(2L, "user B"));
Member member = philzManger.find(1L);
Member member2 = philzManger.find(1L);
System.out.println("(member == member2) = " + (member == member2));
핵심 매니저 (
philz는 제 닉네임입니다)
class PhilzManger {
public static final String NOT_FOUND_MEMBER_EXCEPTION = "[ERROR] 존재하지 않는 Member 입니다.";
private static Map<Long, Member> members = new HashMap<>();
private void persist(Member member) {
members.put(member.getId(), member);
}
private Member find(Class<T> clazz, Long id) {
Member findMember = members.get(id);
if (findMember != null) {
return findMember;
}
throw new IllegalArgumentException(NOT_FOUND_MEMBER_EXCEPTION);
}
}
실행결과
(member == member2) = true
트랜잭션과 읽기 쓰기
트랜잭션이 없어도 읽기는 (find) 가능하며 쓰기 (persist)는 불가능한 것으로 보인다.
