Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- logback
- property
- configmap
- ORM
- liveness probe
- readiness probe
- Probe
- spring boot
- java
- Xmx
- startup probe
- pod
- application properties
- 글또
- jpa
- properties
- Java Virtual Machine
- evicted
- OOM
- Kubernetes
- Heap
- diskpressure
- k8s
- JVM
- xms
- 회고
Archives
- Today
- Total
여우발개발
3장. 영속성 관리 본문
3장의 주된 내용은 영속성 컨텍스트(consistence context)에 대한 이해이다.
entity가 영속 상태이어야 JPA가 제공하는 기능들을 사용할 수 있다.
- JPA가 제공하는 기능
- 엔티티를 매핑하는 설계 부분
- 매핑한 엔티티를 실제 사용하는 부분
3장에서는 매핑한 엔티티를 엔티티 매니저를 통해 어떻게 사용하는지를 탐구한다.
3.1 엔티티 매니저 팩토리와 엔티티 매니저
// 비용이 아주 많이 든다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("{{persistence-unit name}}");
- EntityManagerFactory
- JPA를 동작시키기 위한 기반 객체 생성
- factory라는 이름 답게 비용이 아주 많이 든다
- 비용이 크므로 데이터베이스를 하나만 사용하는 경우 하나만 생성한다
- 여러 스레드가 동시에 접근해도 안전하므로 스레드 간 공유 가능
- hibernate 등 JPA 구현체들은 생성 시 DB connection pool도 만든다
- spring 등 J2EE 환경에서는 해당 컨테이너가 제공하는 데이터소스 사용
// 비용이 적게 든다.
EntityManager em = emf.createEntityMnager();
- EntityManager
- 엔티티 매니저 팩토리에서 생성할 수 있다.
- 여러 스레드에서 동시 접근 시 동시성 문제가 발생하므로 스레드 간 공유 불가
- DB 연결이 꼭 필요한 시점까지 커넥션을 얻지 않는다. (성능적 이점)
3.2 영속성 컨텍스트란?
Persistence Context : 엔티티를 영구 저장하는 환경
→ 하지만… 좀 애매하다. 실제로 DB에 저장되는 것은 아니기 때문이다.
→ 그냥 1차 캐시라고 생각하는게?
em.persist(entity);
- Persistence Context
- EntityManager로 Entity를 저장 or 조회 시 persistence context에 보관/관리된다.
- EntityManager를 통해서 접근, 관리할 수 있다.
3.3 엔티티의 생명주기
Entity entity = new Entity();
entity.setParam("param");
- 비영속 (new/transient - 일시적인, 휘발성)
- persistence context와 전혀 관계가 없는 상태
- persist가 되기 전 새로 만든 entity 객체
em.persist(entity);
- 영속 (managed)
- persistence context에 저장되고, 관리되는 상태
- DB에 저장된 상태와는 다르다. 영속 상태여도 DB 내에 없을 수 있다.
- 조회해온 entity도 영속 상태이다.
em.detach(entity);
em.clear();
em.close();
- 준영속 (detached)
- persistence context가 관리하던 영속 entity가 관리하지 않게 된 상태
- tx가 끝났다거나 할 때 발생. 개발자가 직접 발생시키기는 힘들다.
- 이 상태에서 persistence context의 관리가 안된다는 사실을 인지하는 것이 중요.
em.remove(entity);
- 삭제 (removed)
- entity를 persistence context와 DB에서 삭제한다.
3.4 영속성 컨텍스트의 특징
- 식별자 값
- persistence context는 entity를 식별자(@Id) 값으로 구분함
- 따라서 영속 상태는 식별자 값이 반드시 있어야 한다.
- 데이터베이스 저장
- 보통 tx 커밋 시 persistence context에 새로 저장된 entity를 DB에 반영한다. → flush
- persistence context가 entity를 관리 시 얻는 장점
- 1차 캐시
- 동일성 보장
- tx를 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
3.4.1 엔티티 조회
- 1차 캐시
- 모든 영속 상태의 entity는 persistence context 내부의 cache에 저장됨
- 1차 캐시의 key는 entity의 식별자(@Id) 값
- → 영속 상태는 식별자 값이 반드시 있어야 한다
- 1차 캐시에서 조회
- entity 조회 시 1차 캐시에 있다면 해당 entity 반환
- DB에서 조회
- entity 조회 시 1차 캐시에 없다면 DB에서 조회해 entity를 생성
- 생성한 entity를 1차 캐시에 저장
- 1차 캐시에 저장된 영속 상태의 entity 반환
→ 영속상태의 entity를 조회 시 메모리의 1차 캐시에서 바로 반환받을 수 있기 때문에 성능상 이점
→ 메모리의 인스턴스를 반환받기 때문에 영속 entity의 동일성이 보장됨
3.4.2 엔티티 등록
EntityMnager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// entityManager는 데이터 변경 시 트랜잭션이 필요함
tx.begin();
// persistence context에 등록 (DB에는 저장 안됨)
em.persist(entity);
// commit 시 DB에 INSERT SQL을 보냄
tx.commit();
쓰기 지연 (transactional write-behind)
- EntityManager는 transaction 커밋 전까지 쓰기 지연 SQL 저장소에 모아둠
- Q. INSERT인지 UPDATE인지 어떻게 알지? → @Id(Primary Key)값을 보고 판단함
- transaction 커밋 시 모아둔 쿼리를 DB에 보냄
- 트랜잭션을 지원하는 쓰기 지연이 가능한 이유
- 필요한 SQL을 발생 시 마다 전송하든
- SQL을 모아서 전송하든
- 트랜잭션 안에서 커밋 전에만 수행되면 결과는 같다
- 모아서 전송하면 db 접근이 줄어드므로 성능 최적화가 가능하다
- (참고) spring-data-jpa의 .save() 사용 시
- persist + flush가 같이 일어난다.
- tx를 명시적으로 끝내지 않아도 flush된다.
3.4.3 엔티티 수정
프로젝트가 커지고 요구사항이 늘어나게 되면, 쿼리도 늘어나고 직간접적으로 비즈니스 로직이 SQL에 의존하게 된다.
JPA를 사용하면 entity를 그냥 수정해주면 알아서 처리해준다.
EntityMnager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// entityManager는 데이터 변경 시 트랜잭션이 필요함
tx.begin();
// 영속 entity 조회
Entity entity = em.find(Entity.class, "entity")
entity.setParam("param");
// commit 시 update 된 entity를 체크하고 DB에 UPDATE SQL을 보냄
tx.commit();
변경 감지 (dirty checking)
- entity의 변경사항을 DB에 자동으로 반영함
- 영속 상태의 entity에만 적용된다
- 변경 감지 작동 순서
- entity를 영속화할 때 최초 상태를 snapshot 떠서 보관해둠
- flush 시점에 snapshot과 entity를 비교해 변경된 entity를 찾는다
- 변경된 entity가 있으면 UPDATE SQL을 생성해서 쓰기 지연 SQL 저장소에 보낸다
- transaction 커밋 시 모아둔 쿼리를 DB에 보냄
- 변경 감지로 인해 실행된 UPDATE SQL
- 기본적으로 entity의 모든 필드를 업데이트
- 장점 (단점보다 더 많아서 기본으로 채택)
- 모든 필드를 사용하면 수정 쿼리가 항상 같다
- application 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있음
- DB에 동일한 쿼리를 보내면 이전에 한번 파싱된 쿼리를 재사용함
- 모든 필드를 사용하면 수정 쿼리가 항상 같다
- 단점
- DB에 보내는 데이터 전송량이 증가
- @DynamicUpdate
- 필드가 너무 많거나 데이터가 너무 클 때 수정된 데이터에 대해서만 동적으로 UPDATE SQL을 생성할 수 있음
- 컬럼이 대략 30개 이상이면 더 빠를 수 있다. 직접 테스트해봐야 확실함
- 한 테이블에 컬럼이 30개 이상이라는 것은 테이블 설계 상 책임 분리가 안되었을 가능성이 높다
3.4.4 엔티티 삭제
EntityMnager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// entityManager는 데이터 변경 시 트랜잭션이 필요함
tx.begin();
// 영속 entity 조회
Entity entity = em.find(Entity.class, "entity")
entity.remove();
// commit 시 delete 된 entity를 체크하고 DB에 DELETE SQL을 보냄
tx.commit();
- 등록과 비슷하게 flush 전까지 쓰기 지연 SQL 저장소에 모아둠
- flush 시 모아둔 쿼리를 DB에 보냄
- remove가 호출되는 순간 해당 entity는 consistence context에서 제거된다.
- 재사용하지 않도록 주의!
3.5 플러시
flush : consistence context의 변경 내용을 DB에 반영
- flush를 실행하면 일어나는 일
- 변경 감지 동작
- 영속성 컨텍스트에 있는 모든 entity의 snapshot을 비교
- 감지된 수정된 엔티티는 수정 쿼리 만들어 쓰기 지연 SQL 저장소에 등록
- DB에 쓰기 지연 SQL 저장소의 쿼리 전송 (등록, 수정, 삭제 쿼리)
- 변경 감지 동작
- flush를 실행하는 방법
- 직접 호출
- EntityManager.flush() 를 직접 호출해서 강제 flush
- 테스트 시에나 사용되고 거의 사용되지 않음
- transaction commit 시 자동 호출
- 변경 내용을 SQL로 전달하지 않고 트랜잭션만 커밋하면 DB에 반영되지 않기 때문에 자동 호출
- 객체지향 쿼리(ex. JPQL) 실행 시 자동 호출
- JPQL은 SQL로 변환되어 DB에서 entity를 조회한다
- JPQL의 SQL이 실행되기 전에 변경 사항이 DB에 반영되어야 하기 떄문에 자동 호출
- 직접 호출
- find() 메소드는 flush가 호출되지 않는다
- 이미 변경된 사항이 적용된 1차 캐시를 먼저 보기 때문
3.5.1 플러시 모드 옵션
플러시 모드를 직접 지정할 수 있다. (대부분 AUTO 그대로 사용)
- FlushModeType.AUTO : (default) commit, 쿼리 실행 시 flush
- FlushModeType.COMMIT : commit 시에만 flush → 성능 최적화 시 사용
3.6 준영속
영속상태의 entity가 영속성 컨텍스트에서 분리된 상태
- 준영속 상태는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다
비영속하고 다를 게 없는데 왜 따로 취급하는걸까?
3.6.1 엔티티를 준영속 상태로 전환 : detach()
em.detach(entity);
- 특정 entity를 준영속 상태로 전환
- 메소드 호출 시 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 entity를 관리하기 위한 모든 정보가 제거됨
3.6.2 영속성 컨텍스트 초기화 : clear()
em.clear();
- 해당 영속성 컨텍스트 초기화해 모든 엔티티를 준영속 상태로 전환
- 영속성 컨텍스트가 새로 만들어진 것과 같다
3.6.3 영속성 컨텍스트 종료 : close()
em.close();
- 해당 영속성 컨텍스트 종료해 모든 엔티티를 준영속 상태로 전환
- 영속성 컨텍스트를 사용할 수 없다
- entity는 보통 영속성 컨텍스트가 종료되면서 준영속 상태가 된다
- (직접 준영속을 만드는 일은 드물다)
3.6.4 준영속 상태의 특징
- 영속성 컨텍스트가 관리하지 않는다
- 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 모든 기능이 동작하지 않는다
- 한 번 영속 상태였으므로 식별자 값이 있다
- 굳이 책에서 언급하는 이유는 필요한 데이터가 지연 로딩으로 얻어져야 하는데 트랜잭션이 종료되어 값을 얻지 못하는 경우를 인지시키기 위해서 인 것 같다.
3.6.5 병합 : merge()
- 비영속 상태의 entity를 인자로 받아 영속 상태의 새로운 entity를 받는다
// 준영속 상태의 엔티티를 받아서 새로운 영속 상태의 엔티티를 반환
Entity mergeEntity = em.merge(entity);
- 파라미터로 넘어온 entity의 식별자로 1차 캐시에서 entity를 조회
- 1차 캐시에 entity가 없다면 DB에서 entity를 조회해 1차 캐시에 저장
- 조회한 영속 entity의 모든 데이터를 준영속 entity의 데이터로 변경한다
- 영속 entity를 반환한다
→ ❗파라미터로 넘어온 준영속 entity는 merge 이후에도 준영속이다
// 준영속 상태의 엔티티는 더 이상 사용할 필요가 없다
// 기존 준영속 엔티티를 참조하던 변수를 영속 엔티티로 교체해주는 것이 안전함
entity = em.merge(detachedEntity);
- 비영속 병합
Entity entity = new Entity();
entity = em.merge(entity);
비영속 엔티티도 영속으로 만들 수 있다
- (참고) spring-data-jpa의 .save()는 병합으로 동작한다.
'기술 도서 리뷰 > 자바 ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
4장. 엔티티 매핑 (0) | 2025.03.07 |
---|---|
2장. JPA 시작 (2) | 2025.02.07 |
1장. JPA 소개 (1) | 2025.02.02 |