해당 내용은 인프런 김영한 강사님의 '자바 ORM 표준 JPA 프로그래밍 - 기본편'의 강의를 기반으로 작성했습니다.
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
정적인 요소
DB를 어떻게 설계하고, 객체를 어떻게 설계하고, 어떻게 JPA로 매핑할 것인가?
JPA가 내부에서 어떻게 동작하는가?
일단 JPA의 매커니즘을 이해해보자.
여기까진 지난 번에도 학습한 내용이고...
"JPA를 이해하는 데 가장 중요한 용어"
"엔티티를 영구 저장하는 환경"
즉, 이것을 "엔티티를 영속화" 한다고 하며,
이는 엔티티 객체를 영속성 컨텍스트라는 데에 저장하는 것
EntityManager.persist(entity);
이것은 엔티티 객체를 DB에 저장하는 것 그 이상으로, 영속화하는 것
엔티티 매니저를 생성하면, 그 안에 1:1로 영속성 컨텍스트라는 눈에 보이지 않는 공간이 생김
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
최초 멤버 객체를 생성한 것
: 멤버 객체는 생성했는데 아직 엔티티 매니저엔 넣지 않은 상태
= JPA와 관련 없음
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
영속성 컨텍스트에 관리되는 상태
엔티티 매니저를 얻어와서 엔티티 매니저(영속 컨텍스트)에 객체를 저장한 상태
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 객체를 저장한 상태(영속)
em.persist(member);
위 그림이 된 거임 = 영속 상태
em.persist(member) : 이때 DB에 저장되는 게 아니다!
영속 상태가 될 뿐, 영속 상태가 된다고 해서 쿼리가 날아가는 게 아님
트랜잭션을 커밋하는 시점에 쿼리가 날아감
영속성 컨텍스트에 저장되었다가 분리된 상태
// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
영속성 컨텍스트에서 member 객체 분리
삭제된 상태
// 객체를 삭제한 상태
em.remove(member);
애플리케이션과 데이터베이스 사이의 중간 계층이 있음
-> 버퍼링을 할 수도 있고, 캐싱을 할 수도 있고...
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
// 엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
// 객체를 저장한 상태(영속)
em.persist(member);
~영속된 상태~
영속성 컨텍스트는 내부에 1차 캐시라는 걸 들고 있음
멤버 객체를 저장하고 조회를 하면, JPA는 DB보다 영속성 컨텍스트를 먼저 찾음
-> 캐시에 있는 값을 조회해 옴
Member findMember = em.find(Member.class, "member1");
Member findMember2 = em.find(Member.class, "member2");
엔티티 매니저라는 건 "데이터베이스 트랜잭션 단위로 만들고 트랜잭션이 끝나면 종료"되기 때문에
1차 캐시가 굉장히 짧은 순간에만 이득이 있고,
여러 명, 여러 쓰레드가 사용하거나 공유하는 캐시가 아님(애초 엔티티 매니저가 여러 쓰레드가 공유할 수 없기도 하고...)
현업에서도 큰 도움을 주진 않지만,
이 컨셉이 주는 효과가 있다고 함
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
마치 자바컬렉션에서 똑같은 레퍼런스가 있는 객체를 꺼내면 같은 인스턴스이듯이
1차 캐시된 객체를 꺼내오기 때문에 같은 객체다.
" 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공"
-> JPA에서 같은 트랜잭션 안에서 이렇게 실행하면, == 같은 비교 연산자 사용시 True가 나옴
애플리케이션에서도 트랜잭션 단위로 엔티티를 사용한다는 건가? 라고 생각해봄
쿼리는 persist 때 나가는 것이 아님
-> persist 때마다 DB에 쿼리를 날리면 최적화할 여지조차 없음
데이터베이스에 아무리 데이터를 넣어도 commit을 해야 유효하니,
commit 전에 쿼리를 날림
위 사진처럼 엔티티 매니저에 Member 1과 2를 쌓고 이걸 한 번에 보낼 수 있음(=JDBC배치)
-> 하이버네이트 옵션도 있음
위처럼 설정한 사이즈만큼 모아서 DB에 쿼리를 한 방에 보내고 `commit`함
이걸 실전에서 같은 걸 여러 개 DB에 저장하는 경우가 많지는 않은데,
중요한 건, JPA를 써서 성능을 먹고 들어갈 수 있다는 것
내가 생짜로 쿼리를 짜고,그 쿼리를 모으고, commit 전에 넣기는 너무 힘든데
이렇게 버퍼링을 모아서 write하기
(1) 영속성 컨텍스트의 쓰기 지연 SQL 저장소에 저장
각 객체를 persist하면 모두 쓰기 지연 SQL저장소에 저장
(2) DB에 flush & commit이 됨
애플리케이션에서 commit을 날리면 영속성 컨텍스트에서 DB로 flush와 commit을 날림
JPA의 목적은 자바 컬렉션 다루듯이 객체(엔티티)를 다루는 것
자바 객체를 수정 후 다시 컬렉션에 넣지 않듯, 엔티티도 그렇게!
위처럼 set함수로 값만 바꿨는데도 update쿼리가 나옴
실제로 값도 변경됨
이렇게 할 수 있는 이유가 바로 더티 체킹(Dirty Checking), 즉 변경 감지 기능 때문
JPA가 데이터베이스 트랜잭션을 commit하는 시점에, 내부적으로 flush라는 게 호출이 됨
1차 캐시 안에는 ID와, Entity, 그리고 스냅샷이 있음
스냅샷이란?
내가 값을 읽어온 시점의 값을 떠놓은 것
이 상태에서 내가 'Member1'값을 변경한 후 commit을 하면, 내부적으로 flush가 호출되고,
이때 JPA가 스냅샷과 Entity를 비교하여,
변경사항이 있는 경우 update 쿼리를 쓰기 지연 SQL 저장소에 저장
이를 DB에 반영
변경과 매커니즘이 같은데,
쿼리가 Delete쿼리임
persist가 필요하지 않음update 쿼리가 날아감if (member.getName().equals("ZZZzzz")) {
em.update(member);
}