[김영한 - 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 context
DB와 동기화동기화를 했다 하여 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
)는 불가능한 것으로 보인다.