영속성 컨텍스트의 특징
- 영속성 컨텍스트와 데이터베이스 저장
- JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영한다. 이것을 플러시(flush)라고 한다.
- 영속성 컨텍스트가 엔티티를 관리할때 장점
- 1차 캐시
- 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
1. 엔티티 조회
영속성 컨텍스트는 내부에 캐시를 가지고 있다. 이 캐시를 1차 캐시라 하며 영속 상태의 엔티티는 모두 이곳에 저장된다.
1차 캐시
영속성 컨텍스트 내부에 Map이 존재하며, 키는 @Id, 값은 엔티티 인스턴스이다.
// 엔티티 생성(비영속)
Memeber member = new Member();
member.setId("member1");
member.setUsername("깡냉");
// 엔티티 영속
em.persis(member);
위의 코드를 실행하면 영속 컨텍스트의 1차 캐시는 아래와 같이 된다.(DB에 저장되기 전)
1-1. 1차 캐시에서 조회
Member member = em.find(Member.class, "member1");
em.find() 호출시 , 엔티티가 1차 캐시에 존재하지 않는다면 엔티티 매니저는 데이터베이스를 조회해서 엔티티를 생성한다. 그리고 1차 캐시에 저장한 후에 영속 상태의 엔티티를 반환하게된다.
Member member2 = em.find(Member.class, "member2");
// 1차 캐시에 없으며, DB에 존재시 위 그림처럼 동작함
1-2. 데이터베이스에서 조회
em.find() 호출시 , 엔티티가 1차 캐시에 존재하지 않는다면 엔티티 매니저는 데이터베이스를 조회해서 엔티티를 생성한다. 그리고 1차 캐시에 저장한 후에 영속 상태의 엔티티를 반환하게된다.
2. 엔티티 등록
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
// 영속 상태이며, DB INSERT SQL을 보내지 않음
transaction.commit(); // [트랜잭션] 커밋
엔티티 매니저는 트랜잭션을 커밋하기 전까지 내부 쿼리 저장소에 INSERT SQL 를 차곡차곡 모아둔 후, 트랜잭션 커밋 시점에 모아둔 쿼리를 데이터베이스에 보낸다.
이 것을 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
라고 한다.
두 개의 INSERT SQL을 쓰기 지연 SQL 저장소에 저장해 둔 다음, 트랜잭션 커밋시점에 아래와 같이 영속성 컨텍스트를 플러시(flush)한다. (영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업, 동기화 후 실제 데이터베이스 트랜잭션을 커밋한다.)
3. 엔티티 수정
3-1. SQL 수정(Update)쿼리의 문제점
SQL을 사용하면 수정 쿼리를 직접 작성해야 한다. 그런데 프로젝트가 점점 커지고 요구사항이 늘어나면서 수정 쿼리도 점점 추가된다.이런 방식의 문제점은 수정 쿼리가 많아지는 것은 물론이고 비지니스 로직을 분석하기 위해 SQL을 계속 확인해야 한다. 결국 직접적이든 간접적이든 비지니스 로직이 SQL에 의존하게 된다.
// 초기
update member
set name = ?, age = ?
where id = ?
// 기능추가
update member
set name = ?, age = ?, grade = ?
where id = ?
2개의 SQL을 사용할 것인가, 기능 추가된 SQL(초기 SQL이 포함되므로)만 사용할 것인가.
기능 추가된 SQL을 사용할 경우 실수로 등급을 입력하지 않는다면..? 여러가지 문제가 존재한다.
3-2. 변경감지
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(); // [트랜잭션] 커밋
JPA로 엔티티를 수정할 때는 엔티티를 조회한 후 데이터만 변경하면 된다.
즉, JPA는 em.update()와 같은 update 메소드가 존재하지 않는다.
memberA를 setter로만 변경했을 뿐인데 어떻게 데이터베이스에 반여이 됬을까? 그 이유는 JPA의 변경감지(Dirty Checking)
기능 때문이다.
참고)JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는데 이것을 스냅샷이라 한다.
- 트랜잭션을 커밋하면 엔티티 매니저 내부에서 먼저 플러시(flush())가 호출된다.
- 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾는다.
- 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
- 쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.
- 데이터베이스 트랜잭션을 커밋한다.
변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다. (비영속, 준영속 상태는 영속성 컨텍스트의 관리를 받지 못하는 엔티티기 때문에 값을 변경해도 데이터베이스에 반영되지 않는다.)
3-3. 엔티티 삭제
Member memberA = em.find(Member.class, "memberA");
em.remove(memberA);
em.remove()에 삭제 대상 엔티티를 넘겨주면 엔티티를 삭제한다.물론 엔티티를 즉시 삭제하는 것이 아니라, 엔티티 등록과 비슷하게 삭제 쿼리를 쓰기 지연 SQL 저장소에 등록한다. 이 후 트랜잭션을 커밋하게되면 플러시가 호출되어 실제 데이터베이스에 삭제 쿼리를 전달하게 된다.
em.remove(memberA)를 호출하는 순간 memberA는 영속성 컨텍스트에서 제거된다.
출처 : 김영한 자바 ORM 표준 JPA 프로그래밍
'프로그래밍 노트 > JPA' 카테고리의 다른 글
[JPA] 제네릭(generic)한 컨버터(converter) 만들기. Generic Json Converter (0) | 2019.10.10 |
---|---|
[JPA] 영속성관리_3 (플러시, 준영속) (0) | 2019.09.18 |
[JPA] 영속성 관리_1 (EntityManager, EntityManagerFactory, PersistContext) (0) | 2019.09.03 |
[JPA] 스프링 데이터 JPA 소개 (0) | 2019.09.02 |
[JPA] 객체지향 쿼리 QueryDSL (0) | 2019.08.26 |