| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- Spring Container
- 지연 로딩
- mybatis
- 페이징
- kafka
- JPA
- dockerhub
- Routing Key
- CORS
- DLQ
- @Transactional
- docker
- docker compose
- Dead Letter Queue
- JWT
- DI
- JdbcTemplate
- Spring Data JPA
- 스프링 부트
- Web
- Spring
- 컨테이너
- JPQL
- MSA
- @ComponentScan
- 쿠버네티스
- AWS
- securitycontextholderfilter
- 서블릿 컨테이너
- redis
- Today
- Total
look-forest
영속성 관리 - 내부 동작 방식 본문
JDBC API의 일꾼 Datasource(interface) - hikari(구현체) //connection, sql.
JPA API의 일꾼 EntityManager(interface) - hibernate(구현체) //entity 라이프사이클 관리, 영속성 관리.
엔티티 매니저를 통해 DB를 조작하는 동작 방식에 대해 알아보자

영속성 컨텍스트
데이터베이스에서 조회했거나 저장할 객체들을 보관하는 곳
엔티티를 영구 저장하는 환경
- Entity를 모아두는 공간
- 엔티티 매니저를 통해서 영속성 컨텍스트에 접근
- J2SE 환경 : 엔티티 매니저와 영속성 컨텍스트가 1:1
- J2EE, 스프링 프레임워크 같은 컨테이너 환경 : 엔티티 매니저와 영속성 컨텍스트가 N:1

엔티티의 생명주기
- 비영속 (new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태. 객체를 생성한 상태.
- 영속 (managed) : 영속성 컨텍스트에 관리되는 상태, 객체를 저장/조회한 상태
- 준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제 (removed) : 객체를 삭제한 상태

영속성 컨텍스트의 이점
- 로드된 모든 객체를 identity map에 유지 -> 1차 캐시, 동일성 보장
- 트랜잭션을 짧게 잡기 위한 쓰기 지연 (transactional write-behind) - 쓰기 지연 SQL 저장소
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
1차 캐시


동일성 보장
1차 캐시에서 조회하므로 동일 엔티티. 마치 자바 컬렉션처럼.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
트랜잭션을 지원하는 쓰기 지연
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋


엔티티 수정 - 변경 감지 (Dirty check)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); // [트랜잭션] 커밋

flush
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영 (동기화)
- 영속성 컨텍스트를 비우지 않음
- 트랜잭션이라는 작업 단위 -> 커밋 직전에만 동기화 하면 된다
flush 발생 시
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)
영속성 컨텍스트를 flush하는 방법
- em.flush() - 직접 호출
- 트랜잭션 커밋 - flush 자동 호출
- JPQL 쿼리 실행 - flush 자동 호출
JPQL 쿼리 실행시 flush가 자동으로 호출되는 이유
JPQL는 Java가 아니라 DB와 직접 통신하는 쿼리이기 때문에 DB에서 데이터를 조회해오는데, 영속성 컨텍스트와 값이 다를 수 있기 때문에 데이터 동기화 차원에서 flush한다.
ex) flush를 하지 않으면 아래와 같은 상황에 db에서 데이터를 조회해올 수 없다.
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();
준영속 상태(detached)
영속성 컨텍스트가 더는 관리하지 않는 엔티티.
영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
-> 영속성 컨텍스트가 제공하는 기능을 사용 못함
준영속 상태로 만드는 방법
- em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
- em.clear() : 영속성 컨텍스트를 완전히 초기화
- em.close() : 영속성 컨텍스트를 종료
변경 감지와 병합(merge)
수정을 시도하는 객체도 DB에 한번 저장되어서 식별자가 존재하므로, 넓은 의미에서 준영속 엔티티라고 볼 수 있다.
이렇게 기존 식별자를 가지고 있는 준영속 엔티티를 수정하려면, 변경 감지 기능을 사용하면 된다.
영속성 컨텍스트에서 엔티티를 다시 조회한 후에 영속 상태의 데이터를 수정
트랜잭션 안에서 엔티티를 다시 조회하여 영속상태로 만들고, 수정할 값 변경
-> 트랜잭션 커밋 시점에 변경 감지(Dirty Checking)이 동작해서 데이터베이스에 UPDATE SQL 실행

※참고
준영속 엔티티 수정 시에 Dirty check가 아닌, 병합(merge) 메서드를 사용할 수도 있다.

영속성 컨텍스트에서 엔티티를 다시 조회한 후에 영속 상태의 데이터를 수정하는 코드를 생성해준다고 보면 된다.
*주의*
변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다.
실무에서는 보통 업데이트 기능이 매우 제한적이다. 그런데 병합은 모든 필드를 변경해버리고, 데이터가 없으면 null 로 업데이트 해버린다. 병합을 사용하면서 이 문제를 해결하려면, 변경 폼 화면에서 모든 데이터를 항상 유지해야 한다. 실무에서는 보통 변경 가능한 데이터만 노출하기 때문에, 병합을 사용하는 것이 오히려 번거롭다.
엔티티를 변경할 때는 항상 변경 감지를 사용하자.
참고 자료 & 이미지 출처
자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한 님)
JPA 기반의 애플리케이션 설계 (조영호 님)
'JPA > JPA' 카테고리의 다른 글
| 고급 매핑 (0) | 2024.09.10 |
|---|---|
| 다양한 연관 관계 매핑 (0) | 2024.09.09 |
| 연관관계 매핑 기초 (1) | 2024.09.09 |
| 엔티티 매핑 (1) | 2024.09.08 |
| JPA 시작 (0) | 2024.09.08 |