파게로그
JPA 내부 구조 본문
Pro JPA 2 in Java EE 8: An In-Depth Guide to Java Persistence APIs
CHAPTER 6. Entity Manager
엔티티는 생성된다고 해서 곧바로 저장되지는 않는다. 마찬가지로 엔티티가 GC된다고 해서 곧바로 DB에서 제거되지도 않는다. 이것은 엔티티를 조작해야 하는 어플리케이션이 영속성 라이프사이클을 관리하기 위한 로직이다. JPA는 어플리케이션이 RDB에서 엔티티를 관리하고 검색할 수 있도록 하기 위해서 EntityManager라는 인터페이스를 제공한다.
일견 이는 JPA의 한계로 보인다. persistence runtime이 어떤 객체가 persistent한지 알고 있다면, 어플리케이션이 프로세스에 개입해야만 하는 이유는 무엇일까? Rest는 이러한 디자인이 고의적이며 다른 어떤 transparent persistence solution보다도 어플리케이션에 유익하다고 장담한다. 영속성은 어플리케이션과 persistence provider 사이의 파트너십이다. JPA는 어플리케이션의 능동적인 참여 없이는 얻기 힘든, 제어와 유연성을 제공한다.
2장에서는 EntityManager 인터페이스를 소개하고 엔티티에 대해 사용할 수 있는 기본적인 연산들에 대해 기술했다. 그리고 이를 3장으로 확장시켜 Java EE 환경에 대해 개략적으로 살펴보고 영속성 어플리케이션에 영향을 주는 서비스의 유형에 대해 알아보았다. 마지막으로 4장과 5장에서는 객체에 대한 엔티티를 만드는 데 가장 중요한 object-relational mapping에 대해 기술했다. 이러한 기초 작업들과 함께 우리는, entity managers, persistence contexts, persistence units를 다시 살펴보고 이러한 개념들에 대한 보다 깊은 논의를 할 준비가 되어 있는 것이다.
Persistence Contexts
JPA의 핵심 용어를 다시 살펴보는 것으로 시작하자. persistence unit은 엔티티 클래스에 대한 명명된 설정이다. persistence context는 관리되는 엔티티 인스턴스들의 집합이다. 모든 persistence context는 persistence unit과 연관되어 있는데, 이는 관리되는 인스턴스를 persistence unit에 의해 정의된 집합으로 한정한다. 엔티티 인스턴스가 managed된다는 것은, 그 엔티티 인스턴스가 persistence context에 포함되어 있으며 entity manager에 의해 동작된다는 것을 말한다. 이러한 이유로 우리는 entity manager가 persistence context를 manages한다고 말한다.
persistence context를 이해하는 것은 entity manager를 이해하는 데 있어서 굉장히 중요하다. persistence context로부터 엔티티의 포함 또는 배제는, 해당 엔티티에 대한 persistent operations의 결과를 결정할 것이다. 만약 persistence context가 transaction에 참여한다면, managed되는 엔티티의 메모리 내 상태는 DB와 동기화될 것이다. 그러나 persistence context가 수행하는 이러한 중요한 역할에도 불구하고, persistence context는 어플리케이션에서 결코 실제로 가시적이지는 않다. persistence context는 항상 entity manager를 통해 간접적으로 접근되며, 우리가 필요할 때 거기에 있을 것이라 추정되는 것이다.
지금까진 좋은데, persistence context는 어떻게 생성되며 이는 언제 발생하는가? entity manager는 어떻게 equation을 더하는가(?) 이것이 우리가 흥미를 가지기 시작할 부분이다.
영속성 컨텍스트
유저의 요청이 있을 때마다, EntityManager는 별도로 생성해야 한다. 이는 내부적으로 DB Connection Pool에서 DB를 쓰게 된다. 영속성 컨텍스트는 논리적인 개념으로서 가시적이지는 않지만, 이러한 EntityManager를 통해서 접근할 수 있으며, 곧 '엔티티를 영구 저장하는 환경'을 의미한다.
한편, 같은 transaction이면 같은 영속성 컨텍스트에 접근한다(?)
엔티티의 생명주기
🧶 비영속(new / transient)
▪ 영속성 컨텍스트와 전혀 관계가 없는 상태
▪ 객체를 생성한 상태
Member member = new Member();
member.setId("member1");
member.setUserName("user1");
🧶 영속(managed)
▪ 영속성 컨텍스트에 저장된 상태
▪ 객체를 저장한 상태
em.persist(member);
🧶 준영속(detached)
▪ 영속성 컨텍스트에 저장되었다가 분리된 상태
▪ 회원 엔티티를 영속성 컨텍스트에서 분리
em.detach(member);
🧶 삭제(removed)
▪ 삭제된 상태
▪ 객체를 삭제
em.remove(member);
영속성 컨텍스트의 이점
▪ 1차 캐시
▪ 동일성(identity) 보장
▪ 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
▪ 변경 감지(dirty checking)
▪ 지연 로딩(lazy loading)
엔티티를 persist하면, id에는 엔티티의 id가, value에는 엔티티가 들어가 1차 캐시에 저장된다. 조회를 시도할 때, DB에 직접 접근하기 전 1차 캐시를 먼저 찾아본다. 다만 이 캐시는 트랜잭션 시작부터 끝까지만 유지되는 굉장히 단기적인 캐시이다.
물론 1차 캐시에 없는 경우 DB에 직접 접근하고, DB의 데이터를 1차 캐시에 저장하고, 이를 반환하는 과정을 거친다.
Java에서 이렇게 생성된 객체가 equals() 메서드를 사용하지 않고도 동일성 비교가 가능한 것은, 1차 캐시 덕분이다.
한편 영속성 컨텍스트 내에는 쓰기 지연 SQL 저장소가 있는데, 이는 마치 버퍼처럼 기능한다.
기다리다가 commit() 때 DB에 보낸다.
DB에 보내는 과정을 flush라고 한다.
flush는 1차 캐시에서 지우는 게 아니다. 쓰기 지연 SQL 저장소에 있던 것을 보내는 역할만 한다.
실제 쿼리를 보낸 다음에, 그 후 commit된다.
*clear는 영속성 컨텍스트의 캐시를 비우는 것
update를 명시하는 코드가 없어도 되는 것은, dirty checking 덕분이다.
1차 캐시에 넣을 때, 스냅샷을 떠둔다.
JPA가 commit 또는 flush하는 시점에, 스냅샷과 실제 객체를 모두 비교해서,
바뀐 것은 모두 UPDATE 쿼리를 생성해서 DB에 보낸다.
이렇게 복잡한 과정을 거치는 것은.. 사용자 입장에서 마치 Java Collections의 객체를 다루는 느낌을 받도록 하기 위해서다.
tx commit 시점에 삭제된다.
다만 꼭 일괄적으로 한번에 네트워크를 통해 쿼리가 전송되는 것은 아니다. 이는 동일 테이블, JDBC 드라이버의 지원, DB의 지원, 여러 옵션 등이 맞아떨어질 때 이야기다.
이 때에는 DB에 반영이 되지 않아서 select가 되지 않는다.
그래서 JPA에서는... JPQL을 실행하면 자동으로 flush되도록 설정되어 있다. (commit은 아님)
다만 MyBatis나 Spring JDBC Template 등을 사용할 때에는 자동 flush 호출이 지원되지 않기에 수동으로 해주어야 한다.
영속성 컨텍스트를 비우는 것은 clear이다!
flush가 가능하는 것은 transaction이라는 작업 단위가 있기 때문.
======================
멤버만 사용하는 경우에는 굳이 팀을 함께 조회할 필요가 없다. -> fetch = FetchType.LAZY
LAZY로 조회하게 되면, Team은 null은 안될거고, 프록시 객체(가짜 객체)가 들어온다.
다만 현업에서는 모두 LAZY로 건다.
대신 fetch join이라는 것을 사용한다. 조회하는 시점에, 동적으로 조회할 수 있도록.
Team을 즉시 불러오다가, Team에 또 즉시로딩이 거렬있으면 연쇄적으로 쿼리가 날라간다.
그래서 모두 지연로딩으로 하고, '꼭' 필요한 경우에만 즉시로딩 사용.
지연로딩을 하려면, 프록시 객체가 동작해야하는데, 그러기 위해서는 영속성 컨텍스트가 살아있어야 한다.
만약 em.close(); 이후
Team findTeam = findMember.getTeam();
findTeam.getName(); 을 실행하면
LazyInitializationException이 발생한다. 영속성 컨텍스트에 의해 관리되고 있지 않은 객체를 지연로딩을 시도했기 때문.
** Spring Framework 사용 시... tx 끝난 시점에 Spring이 영속성 컨텍스트를 다 없앤다. 그 후 Controller에서 lazy loading하려고 할 때, LazyInitializationException이 발생한다. 필요 시 미리 터치해서 로딩..
프록시와 즉시로딩, 지연로딩
'콤퓨타 왕기초 > JPA' 카테고리의 다른 글
실습 4 매핑 및 JPQL (0) | 2021.06.02 |
---|---|
JPA와 객체지향 쿼리 (0) | 2021.06.02 |
연관관계 매핑 (0) | 2021.05.28 |
실습 3 (Mapping annotations) (0) | 2021.05.27 |
[실습] Mapping to DDL (0) | 2021.05.27 |