영속성 컨텍스트
전에 썻던 글처럼 EntityManagerFactory 에서 EntityManager 를 생성하여 커넥션풀에서 미리 연결를 하고 있는 디자인 패턴에서 하나를 사용하여 DB 에 데이터 CRUD 를 하게 된다.
그렇다면 영속성 컨텍스트는 무엇일까?
- Entity를 영구 저장하는 환경 이라는 뜻
- EntityManager.persist(entity) -> 영속성 컨텍스트에 저장한다는 구문
- 논리적인 개념으로 EntityManager 를 통해서 영속성 컨텍스트에 접근한다.
Spring 프레임워크는 아래와 같은 구조를 가지고 있다.
Entity 생명 주기
비영속 (new/transient) 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
영속 (managed) 영속성 컨텍스트에 관리되는 상태
준영속 (detached) 영속성 컨텍스트에 저장되었다가 분리된 상태
- em.detache(member) // 이런식으로 영속성 컨텍스트에서 분리하는것을 말한다.
삭제 (removed) 삭제된 상태
- em.remove(member) // DB 에 데이터를 지우는 상태를 말한다.
try {
Member member = new Member(); -> 비영속 상태
member.setId(1L);
member.setName("뀨?");
System.out.println("전");
em.persist(member); -> 영속 상태
System.out.println("후");
tx.commit();
}catch (Exception e){
tx.rollback();
}finally {
em.close();
}
콘솔에 찍힌 값을 보게 되면 "전", "후" 가 출력한 뒤에 insert 쿼리가 날라가게 된다.
이때 알 수 있는점이 바로 쿼리를 날리는것이 아닌 영속성 컨텍스트에 저장을 하고 한꺼번에 쿼리를 날리게 하는것이다.
영속성 컨텍스트의 이점
- 1차 캐시
- 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
1차 캐시
위의 사진처럼 만약 member을 영속성 컨텍스트에 저장을 한다면 조회할때, 1차캐시에서 확인을 한뒤에 없으면 DB 쿼리문을 날리고 있다면 1차캐시에서 꺼내서 값을 전달해준다.
만약 member2 를 조회하게 되면 DB 에서 꺼내오고 1차캐시에 저장이 된다.
다만, 이러한 점은 그렇게 큰 이점은 없다. 왜냐하면 트랜잭션 하나의 영속 컨텍스트 하나에서 사용하는것이지, 다른 요청으로 인해 트랜잭션이 만들어지고 그 트랜잭션에서 사용하게 된다면 다른 트랜잭션의 1차캐시를 사용할 수 없다.
하지만, 전체적인 영속 컨텍스트를 사용하는 방법도 있다 바로 2차캐시이다. 2차캐시는 더 공부하고 블로그에 글을 올려보겠다!
동일성(identity) 보장
위와 같이 영속성 컨텍스트에서 꺼내오는 값들을 전부 같은 값으로 동일성을 보장해준다.
이 같은 이유는 Member a, b 의 값은 참조값이다. 실제로 값이 저장되어 있는곳은 영속성 컨텍스트의 1차캐시이므로
당연히 값은 똑같다. a 의 값을 바꿔도 b 의 값이 바뀐 상태로 값을 쓸수 있다.
INSERT 쿼리
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin();
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit();
위와 같이 EntityManager 에 있는 1차캐시에 저장을 한뒤 트랜잭션 커밋을 하게 되면 INSERT 쿼리문이 나가 DB 에 저장되는 식이다.
UPDATE 쿼리 ( 더티 체킹 )
더티 체킹이란 Repository 에서 update() 를 안해도 값이 변경되면 JPA 에서 감지하고 UPDATE 쿼리를 날려주는것이다.
이게 왜 가능하냐면 1차캐시 덕분이다. 1차캐시에 데이터를 저장되어 있는 데이터가 변경되면 스냅샷과 비교하여 UPDATE 쿼리를 날려 우리가 더티 체킹이 가능하다.
flush (플러시)
*flush(플러시) : 영속성 컨텍스트의 변경내용을 데이터베이스에 반영
*발생하는 떄 : 변경 감지, 수정된 엔티티 쓰기 지연 SQL 저장소에 등록, 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)
em.flush() - 직접 호출
트랜잭션 커밋 - 플러시 자동 호출
JPQL 쿼리 실행 - 플러시 자동 호출
또한, flush 옵션이 있다.
em.setFlushMode(FlushModeType.COMMIT)
FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시 (기본값)
FlushModeType.COMMIT : 커밋할 때만 플러시
준영속 상태로 만드는 방법
em.detach(entity)
특정 엔티티만 준영속 상태로 전환
em.clear()
영속성 컨텍스트를 완전히 초기화
em.close()
영속성 컨텍스트를 종료
공부하고 느낀점
JPA를 사용하면서 EntityManagerFactory 안에 EntityManager가 있고 EntityManager 와 트랜잭션을 이용하여 쿼리를 생성하고 쿼리로 데이터 CRUD 하는것을 알고 있었다. 하지만 실제로 트랜잭션을 생성하고 flush() 를 통해 쿼리문을 나가는것을 보고 편리하다고 느꼈다.
하지만, 2차캐시를 활성화시켜 해보고 싶은 마음이 있었다. 여기서 고려해야할점은 DB와 2차캐시의 데이터 일관성을 어떻게 유지할것인가의 문제가 있을 거고 만약 업데이트 및 생성은 2차캐시에도 반영하고 DB 도 반영한다면 삽입은 느리지만, 조회는 빠를것이라고 생각해보았다.
기초부터 탄탄히 하자는 의미로 JPA 공부를 계속 할것 같다.
'Spring' 카테고리의 다른 글
JPA - Entity 매핑 (2) | 2024.01.05 |
---|---|
JPA - Entity 매핑 (데이터베이스 스키마 자동 생성) (0) | 2024.01.04 |
JPA - 구동 방식 (1) | 2023.12.27 |
JPA - 기본 개념 (0) | 2023.12.27 |
@Conditional (0) | 2023.12.21 |