JPA - 영속성 컨텍스트(Persistence Context)


JPA 영속성 컨텍스트(Persistence Context) 란?

  • 엔티티를 영구 저장하는 환경
  • 영속성 컨텍스트는 논리적인 개념으로 눈에 보이지 않고, 엔티티 매니저를 통해서 영속성 컨텍스트에 접근함

엔티티의 생명주기

비영속(new/transient)

JPA 와 전혀 관련 없이 객체만 생성된 상태가 비영속 상태

1
2
3
var member = Member()
member.id = 1L
member.name = "helloA"

영속(managed)

영속성 컨텍스트에 관리되는 상태
영속성 상태라고 DB에 저장 되는 것이 아니고, commit 이후에 DB에 저장 된다.

1
2
3
4
5
6
7
8
9
10
val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()

// 객체를 생성한 상태(비영속)
var member = Member()
member.id = 1L
member.name = "helloA"

// 객체를 저장한 상태(영속)
em.persist(member)

준영속(detached)

영속성 컨텍스트에 저장되었다가 분리된 상태

1
2
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member)

삭제(removed)

상제된 상태

1
2
//객체를 삭제한 상태(삭제)
em.remove(member)

엔티티 조회

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
31
32
33
var member = Member()
member.id = "memeber1"val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()
val tx = em.transaction

tx.begin()

try {
var memberA = Member()
memberA.id = 1L
memberA.name = "helloA"

em.persist(memberA)
// 강제 DB Insert
em.flush()

val member = em.find(Member::class.java, 1L)
member.name = "helloUpdate"

tx.commit()
} catch (e: Exception) {
tx.rollback()
} finally {
em.close();
emf.close();
}
member.name = "회원1"

// 1차 캐시에 저장됨
em.persist(member)

// 1차 캐시에서 조회
val findMember = em.find(Member::class.java, "member1")

em.persist(member)1차 캐시에 저장 후 find("memeber1") 하게되면 DB를 조회하는게 아니고 1차 캐시를 조회함

1
2
3
// DB에 member1 값이 있다고 가정
val findMember1 = em.find(Member::class.java, "member1")
val findMember2 = em.find(Member::class.java, "member1")
  1. find("memeber2")를 하면 영속성 컨텍스트 안 1차 캐시를 조회
  2. 1차 캐시에 값이 없으면 DB를 조회
  3. 1차 캐시에 member2 저장
  4. member2를 반환
  5. 한번 1차 캐시에 저장되면 member2 조회시 1차 캐시에서 조회
  6. find로 2번 호출하는데 실제로 select쿼리는 한번 발생, 이유는 findMember2는 1차 캐쉬에서 조회
    주의점) 트랙잰션 단위로 움직임

영속 엔티티의 동일설 보장

동일한 트랜잭션 안에서는 1차캐시에 저장되어있기 때문에 동일한 주소값을 참조(Collection에 똑같은 객체를 사용하는것과 같음)

1
2
3
4
5
// DB에 member1 값이 있다고 가정
val findMember1 = em.find(Member::class.java, "member1")
val findMember2 = em.find(Member::class.java, "member1")

println(findMember1 == findMember2) // 결과값: true

트랙잭션을 지원하는 쓰기 지연

JPA는 트랜잭션을 하기전까지 영속성 컨텍스트 안에 데이터를 쌓고 있다.
트랙잭션 commit을 해야 SQL을 데이터베이스로 보낸다.

  1. persistmemberA를 넣으면 1차 캐시에 저장과 동시에 memberA에 Entity를 분석하여 SQL 문을 생성하여 쓰기 지연 SQL 저장소에 저장
  2. memberB도 동일하게 SQL문을 생성하여 쓰기 지연 SQL 저장소 저장
  3. memberAmemberB쓰기 지연 SQL 저장소에 쌓인다

  1. commit()을 실행하면 쓰기 지연 SQL 저장소에 있던 SQL문이 flush로 데이터 베이스에 전달

실행 결과

wrtieBehind.kt
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
val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()
val tx = em.transaction

tx.begin()

try {
var memberA = Member()
memberA.id = 1L
memberA.name = "helloA"

var memberB = Member()
memberB.id = 2L
memberB.name = "helloB"

em.persist(memberA)
em.persist(memberB)
println("=============== persist")

tx.commit()
} catch (e: Exception) {
tx.rollback()
} finally {
em.close();
emf.close();
}
result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
=============== persist
Hibernate:
/* insert entity.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
Hibernate:
/* insert entity.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
  • 주석처리 한 부분에서 insert 쿼리문이 실행 되지 않고 commit 시점에 sql문 실행
  • hibernate.jdbc.batch_size 옵션으로 쓰기지연 SQL 저장소에 쌓이면 처리 갯수 설정 가능(버퍼링 기능이나 실전에서 잘 사용하지 않음)

Entity 수정(변경 감지)

  1. 데이터가 처음 읽어온 상태를 스냅샷으로 저장
  2. commit 시점에서 Entity스냅샷을 비교 하여 변경 된 부분의 update 문을 DB에 전달
dirtyChecking.kt
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
val emf = Persistence.createEntityManagerFactory("hello")
val em = emf.createEntityManager()
val tx = em.transaction

tx.begin()

try {
var memberA = Member()
memberA.id = 1L
memberA.name = "helloA"

em.persist(memberA)
// 강제 DB Insert
em.flush()

val member = em.find(Member::class.java, 1L)
member.name = "helloUpdate"

tx.commit()
} catch (e: Exception) {
tx.rollback()
} finally {
em.close();
emf.close();
}
result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Hibernate: 
/* insert entity.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
Hibernate:
/* update
entity.Member */ update
Member
set
name=?
where
id=?
  • JPA는 update 코드가 있는게 아니라 Collection 처럼 변경되는 시점을 자동으로 감지해서 수정

Entity 삭제

변경 감지와 동일하게 작동

1
2
val member = em.find(Member::class.java, 1L)
em.remove(member)

플러시(Flush)

  • 영속성 컨텍스트를 비우지 않음
  • 영속성 컨텍스트의 변경내용을 DB에 동기화
  • commit 직전에만 동기화

준영속 상태

JPA가 영속성 컨텍스트에서 분리

1
2
val member = em.find(Member::class.java, 1L)
em.detach(member)

소스코드

참조