Posts [자바 ORM 표준 JPA 프로그래밍-기본편] 영속성 컨텍스트
Post
Cancel

[자바 ORM 표준 JPA 프로그래밍-기본편] 영속성 컨텍스트

본 포스팅은 인프러의 JPA 기본편을 수강하고 정리하는 내용입니다.


JPA에서 가장 중요한 2가지

  • 객체와 관게형 데이터베이스 매핑하기(Object Relational mapping)
    • DB를 어떻게 설계하고 객체를 어떻게 설계해서 중간에 어떻게 JPA로 매핑해서 쓸건지
  • 영속성 컨텍스트
    • 실제 내부에서 JPA가 어떻게 동작하는지

엔티티 매니저 팩토리와 엔티티 매니저

image

EntityManagerFactory를 통해 고객의 요청이 올때마다 EntityManager를 생성하고 내부적으로 DB 커넥션을 사용해서 DB를 사용하게 된다.

영속성 컨텍스트

  • JPA를 이해하는데 가장 중요한 용어
  • 엔티티를 영구 저장하는 환경 이라는 뜻
  • EntityManager.persist(entity);
    • persist() 메서드는 실제로 DB에 저장한다기보단 영속성 컨텍스트에 저장한다.

엔티티 매니저? 영속성 컨텍스트?

  • 영속성 컨텍스트는 논리적인 개념
  • 눈에 보이지 않는다.
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근

image

EntityManager 를 생성하면 그안에 1대1로 PersistenceContext(영속성 컨텍스트)가 생성된다.

엔티티의 생명주기

  • 비영속 (new/transient)
    • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
  • 영속 (managed)
    • 영속성 컨텍스트에 관리되는 상태
  • 준영속 (detached)
    • 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제 (removed)
    • 삭제된 상태

image

비영속(비영속 (new/transient))

image

1
2
3
4
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

일반적으로 객체를 생성 후 persist()메소드를 호출하지 않은 상태를 말한다.

영속 (managed)

image

1
2
3
4
5
6
7
8
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);

생성한 객체를 영속성 컨텍스트에서 관리하고 있는 상태를 말한다. 영속 상태가 된다고 바로 DB에 쿼리가 날라가는게 아니라 커밋할 때 쿼리가 날라가 실제 DB에 반영된다.

준영속 (detached)

회원 엔티티를 영속성 컨텍스트에서 분리한 준영속 상태

1
em.detach(member);

삭제 (removed)

객체를 삭제한 상태(삭제)를 말한다.

1
em.remove(member);

영속성 컨텍스트의 이점

애플리케이션과 RDB사이의 중간의 계층이다보니 버퍼링이나 캐슁을 할 수 있다.

  • 1)1차 캐시
  • 2)동일성(identity) 보장
  • 3)트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
  • 4)변경 감지(Dirty Checking)
  • 5)지연 로딩(Lazy Loading)

1. 1차 캐시

image

1
2
3
4
5
6
//엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//엔티티를 영속
em.persist(member);

영속성 컨텍스트는 내부에 1차 캐시 라는 것을 두고 있다. @Id가 PK를 가지고 있고 , Entity가 객체 자체를 가리키게 된다.

1차 캐시에서 조회

image

1
2
3
4
5
6
7
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");

Member 객체를 저장해놓고 조회를 하면 먼저 1차 캐시를 뒤진다. 그래서 1차 캐시에 존재하면 캐시에 있는 값을 그냥 조회해 온다. (훨씬 조회 속도가 빠르다) 만약 1차 캐시에 존재하지 않는다면 그 다음으로 DB에서 찾게 된다.

데이터베이스에서 조회

image

만약 member2를 조회한다하면 1차 캐시에 없기에 DB에서 조회하게 된다. 찾은 데이터를 1차 캐시에 저장 후 반환하게 된다.

Note: 사실 이게 큰 도움은 안된다. 왜냐하면 EntityManager라는 것은 DB 트랜잭션 단위로 만들고 DB트랜잭션이 끝날때마다 삭제된다. 즉, 고객의 요청이 하나 들어와서 비즈니스가 끝나면 영속성 컨텍스트를 지운다. 굉장히 복잡할땐 도움이 될 수 있겠지만 일반적으로 굉장히 짧은 시간 동안 유지하기에 큰 도움은 없다고 볼 수 있다.

2. 동일성(identity) 보장

1
2
3
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다. 꼭 같은 트랜잭션 안에서만 적용된다.

3. 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

1
2
3
4
5
6
7
8
9
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋

내부적으로 버퍼를 가지고 있기에 버퍼에 쭉쭉 엔티티를 쌓다가 커밋하는 순간에 DB에 보낸다.

image

영속성 컨텍스트는 내부적으로 1차 캐시 뿐만 아닌 쓰기 지연 SQL 저장소가 존재한다.

MemberA를 persist()메서드를 통해 저장하면 MemberA가 1차 캐시에 들어감과 동시에 JPA가 엔티티를 분석해서 insert쿼리를 생성해서 쓰기 지연 SQL 저장소 에 쌓아둔다.

MemberB를 저장해도 위처럼 동일하게 작동한다. 그렇다면 언제 쿼리가 DB로 날라가느냐?

image

트랜잭션을 커밋 하는 시점에 쓰기 지연 SQL 저장소에 있던 쿼리들이 flush되면서 쿼리가 DB로 날라간다.

4. 변경 감지(Dirty Checking)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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는 변경 감지라는 기능으로 entity를 변경할 수 있는 기능이 제공된다. 자바 컬렉션처럼 데이터를 조회해서 값을 바꿔도 다시 persist()를 호출해서 저장할 필요가 없다. 비밀은 영속성 컨텍스트 안에 다 있다.

image

JPA는 데이터 베이스 트랜잭션을 커밋하는 시점에 내부적으로 flush() 메서드가 호출된다. 그러면 엔티티와 스냅샷을 비교(1차 캐시 안에 존재) 한다. 1차 캐시는 엔티티가 처음 영속성 컨텍스트가 저장된 상태를 스냅샷에 저장한다. 비교 후 상태 값이 변경되었으면 update쿼리를 쓰기 지연 SQL 저장소 에 만들어두고 DB에 반영하고 커밋한다.

5. 엔티티 삭제

1
2
3
4
//삭제 대상 엔티티 조회 (위의 매커니즘과 동일하고 트랜잭션 커밋 시점에 delete 쿼리가 호출된다)
Member memberA = em.find(Member.class, “memberA");

em.remove(memberA); //엔티티 삭제
This post is licensed under CC BY 4.0 by the author.

[자바 ORM 표준 JPA 프로그래밍-기본편] JPA 소개

[자바 ORM 표준 JPA 프로그래밍-기본편] 플러시