Persistence Context


JPA μ—μ„œ κ°€μž₯ μ€‘μš”ν•œ 2가지

JPA λ₯Ό μ‚¬μš©ν•˜λ©΄μ„œ κ°€μž₯ μ€‘μš”ν•œ 것은 β€œκ°μ²΄μ™€ RDB μ–΄λ–»κ²Œ 맀핑할 것인가” 이닀. 즉, 객체와 DB λ₯Ό μ–΄λ–»κ²Œ 섀계할 것인지에 λŒ€ν•œ 정적인 관점을 μ΄ν•΄ν•˜κΈ° μœ„ν•΄ Object Relational Mapping 에 λŒ€ν•΄ μ΄ν•΄ν•˜λŠ” 것이 μ€‘μš”ν•˜λ‹€. 그리고, μ‹€μ œ JPA κ°€ λ‚΄λΆ€μ μœΌλ‘œ μ–΄λ–»κ²Œ λ™μž‘ν•˜λŠ”μ§€ μ΄ν•΄ν•˜κΈ° μœ„ν•΄ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— λŒ€ν•΄ 이해해야 ν•œλ‹€.

μ—”ν‹°ν‹° λ§€λ‹ˆμ € νŒ©ν† λ¦¬μ™€ μ—”ν‹°ν‹° λ§€λ‹ˆμ €

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ 뭐야?

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλž€ μ—”ν‹°ν‹°λ₯Ό 영ꡬ μ €μž₯ν•˜λŠ” ν™˜κ²½μ΄λΌκ³  생각할 수 있으며, JPA λ₯Ό μ΄ν•΄ν•˜λŠ”λ° κ°€μž₯ μ€‘μš”ν•œ μš©μ–΄μ΄λ‹€. EntityManager.persist(entity); ν˜ΈμΆœμ€ λ‹¨μˆœνžˆ DB 에 μ €μž₯ν•˜λŠ” 것이 μ•„λ‹Œ, μ—”ν‹°ν‹°λ₯Ό μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλΌλŠ” 곳에 μ €μž₯ν•˜κ² λ‹€λŠ” 의미λ₯Ό 가지고 μžˆλ‹€.

μ—”ν‹°ν‹° λ§€λ‹ˆμ €? μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ?

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” 논리적인 κ°œλ…μœΌλ‘œ λˆˆμ— 보이지 μ•ŠλŠ”λ‹€. JPA λ₯Ό μ‚¬μš©ν•˜λŠ” 것은 μ—”ν‹°ν‹° λ§€λ‹ˆμ €λ₯Ό ν†΅ν•΄μ„œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ ‘κ·Όν•˜λŠ” 것이라고 생각할 수 μžˆλ‹€.

μ—”ν‹°ν‹°μ˜ 생λͺ…μ£ΌκΈ°

μ—”ν‹°ν‹°μ˜ 생λͺ…μ£ΌκΈ°λŠ” 총 4κ°€μ§€λ‘œ λΆ„λ₯˜λ  수 μžˆλ‹€.

  • λΉ„μ˜μ†(new/transient) μƒνƒœλŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ™€ μ „ν˜€ 관계가 μ—†λŠ” μƒˆλ‘œμš΄ μƒνƒœλ₯Ό 의미
  • μ˜μ†(managed) μƒνƒœλŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ˜ν•΄ κ΄€λ¦¬λ˜λŠ” μƒνƒœλ₯Ό 의미
  • μ€€μ˜μ†(detached) μƒνƒœλŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ €μž₯λ˜μ—ˆλ‹€κ°€ λΆ„λ¦¬λœ μƒνƒœλ₯Ό 의미
  • μ‚­μ œ(removed) μƒνƒœλŠ” μ‚­μ œλœ μƒνƒœλ₯Ό 의미

λΉ„μ˜μ†

// 객체λ₯Ό μƒμ„±ν•œ μƒνƒœ(λΉ„μ˜μ†)
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.detach(member);
// 객체λ₯Ό μ‚­μ œν•œ μƒνƒœ(μ‚­μ œ)
em.remove(member);

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ 이점

  • 1μ°¨ μΊμ‹œ
    • 1μ°¨ μΊμ‹œμ—μ„œ μ‘°νšŒκ°€ κ°€λŠ₯ν•˜λ©° 1μ°¨ μΊμ‹œμ— μ—†λ‹€λ©΄ DB μ—μ„œ μ‘°νšŒν•˜μ—¬ 1μ°¨ μΊμ‹œμ— 올렀 λ†“λŠ”λ‹€.
  • 동일성(Identity) 보μž₯
    • 동일성 비ꡐλ₯Ό 보μž₯ν•œλ‹€.
  • νŠΈλžœμž­μ…˜μ„ μ§€μ›ν•˜λŠ” μ“°κΈ° 지연(Transactional Write-Behind)
    • νŠΈλžœμž­μ…˜μ„ μ§€μ›ν•˜λŠ” μ“°κΈ° 지연이 κ°€λŠ₯ν•˜λ©° νŠΈλžœμž­μ…˜ μ»€λ°‹ν•˜κΈ° μ „κΉŒμ§€ 쿼리λ₯Ό λ°”λ‘œ 보내지 μ•Šκ³  λͺ¨μ•„μ„œ 보낼 수 μžˆλ‹€.
  • λ³€κ²½ 감지(Dirty Checking)
    • 1μ°¨ μΊμ‹œμ— λ“€μ–΄μ˜¨ λ°μ΄ν„°μ˜ μŠ€λƒ…μƒ·μ„ 찍어두어 컀밋 μ‹œμ μ˜ 엔티티와 μŠ€λƒ…μƒ·μ„ λΉ„κ΅ν•˜μ—¬ UPDATE 쿼리λ₯Ό μƒμ„±ν•œλ‹€.
  • 지연 λ‘œλ”©(Lazy Loading)
    • μ—”ν‹°ν‹°μ—μ„œ ν•΄λ‹Ή μ—”ν‹°ν‹°λ₯Ό 뢈러올 λ•Œ 쿼리λ₯Ό λ‚ λ € ν•΄λ‹Ή 데이터λ₯Ό κ°€μ Έμ˜¨λ‹€.

μ—”ν‹°ν‹° 쑰회, 1μ°¨ μΊμ‹œ

// μ—”ν‹°ν‹°λ₯Ό μƒμ„±ν•œ μƒνƒœ(λΉ„μ˜μ†)
Member member = new Member();
member.setId("member1");
member.setUsername("νšŒμ›1");
// μ—”ν‹°ν‹°λ₯Ό μ˜μ†, 1μ°¨ μΊμ‹œμ— μ €μž₯딀
em.persist(member);
// 1μ°¨ μΊμ‹œμ—μ„œ 쑰회
Member findMember = em.find(Member.class, "member1");
// DB μ—μ„œ 쑰회
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

μ—”ν‹°ν‹° 등둝 μ‹œ νŠΈλžœμž­μ…˜μ„ μ§€μ›ν•˜λŠ” μ“°κΈ° 지연

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// μ—”ν‹°ν‹° λ§€λ‹ˆμ €λŠ” 데이터 λ³€κ²½μ‹œ νŠΈλžœμž­μ…˜μ„ μ‹œμž‘ν•΄μ•Ό ν•œλ‹€.
transaction.begin(); // [νŠΈλžœμž­μ…˜] μ‹œμž‘
em.persist(memberA);
em.persist(memberB);
// μ—¬κΈ°κΉŒμ§€ INSERT SQL을 λ°μ΄ν„°λ² μ΄μŠ€μ— 보내지 μ•ŠλŠ”λ‹€.
// μ»€λ°‹ν•˜λŠ” μˆœκ°„ λ°μ΄ν„°λ² μ΄μŠ€μ— INSERT SQL을 보낸닀.
transaction.commit(); // [νŠΈλžœμž­μ…˜] 컀밋

memberA κ°€ 1μ°¨ μΊμ‹œμ— λ“€μ–΄κ°€κ³  INSERT SQL λ§Œλ“€μ–΄μ„œ μ“°κΈ° 지연 SQL μ €μž₯μ†Œμ— λ„£μ–΄λ‘”λ‹€. memberB κ°€ 1μ°¨ μΊμ‹œμ— λ“€μ–΄κ°€κ³  INSERT SQL λ§Œλ“€μ–΄μ„œ μ“°κΈ° 지연 SQL μ €μž₯μ†Œμ— λ„£μ–΄λ‘”λ‹€. μ»€λ°‹λ˜λŠ” μ‹œμ μ— flush ν›„ commit 을 μ§„ν–‰ν•œλ‹€. μ΄λŠ” JDBC Batch 와 같은 역할을 ν•˜κ³  μžˆλ‹€. Hibernate μ—λŠ” size 만큼 λͺ¨μ•„μ„œ ν•œλ°©μ— DB 에 μ»€λ°‹ν•˜λŠ” μ˜΅μ…˜μ΄ μ‘΄μž¬ν•˜κΈ°λ„ ν•œλ‹€.

μ—”ν‹°ν‹° μˆ˜μ • λ³€κ²½ 감지

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(); // [νŠΈλžœμž­μ…˜] 컀밋

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ μ—”ν‹°ν‹°λ₯Ό κ΄€λ¦¬ν•˜κ³  있기 λ•Œλ¬Έμ— 변경을 감지할 수 μžˆλ‹€. 변경사항이 있으면 컀밋 μˆœκ°„μ— UPDATE 쿼리λ₯Ό λ§Œλ“€μ–΄μ„œ λ‚ λ¦°λ‹€.

μ–΄λ–»κ²Œ 변경사항을 κ°μ§€ν•˜λŠ” 걸까?

1μ°¨ μΊμ‹œμ—λŠ” μŠ€λƒ…μƒ·μ΄λΌλŠ” 곡간이 μžˆλ‹€. 값이 1μ°¨ μΊμ‹œμ— λ“€μ–΄μ˜¨ 졜초 μ‹œμ μ— μŠ€λƒ…μƒ·μ„ μ°μ–΄λ†“λŠ”λ‹€. νŠΈλžœμž­μ…˜μ΄ μ»€λ°‹λ˜λŠ” μ‹œμ μ— JPA κ°€ 엔티티와 μŠ€λƒ…μƒ·μ„ λΉ„κ΅ν•˜μ—¬ λ³€κ²½ 사항이 μ‘΄μž¬ν•  경우 UPDATE SQL 생성 ν›„ 컀밋을 μ§„ν–‰ν•œλ‹€.

μ—”ν‹°ν‹° μ‚­μ œ

// μ‚­μ œ λŒ€μƒ μ—”ν‹°ν‹° 쑰회
Member memberA = em.find(Member.class, β€œmemberA");
em.remove(memberA); // μ—”ν‹°ν‹° μ‚­μ œ

ν”ŒλŸ¬μ‹œ


ν”ŒλŸ¬μ‹œλž€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ λ³€κ²½λ‚΄μš©μ„ DB 에 λ°˜μ˜ν•œλ‹€λŠ” μ˜λ―Έμ΄λ‹€. ν•˜μ§€λ§Œ, νŠΈλžœμž­μ…˜μ΄ μΌμ–΄λ‚˜κΈ° μ „μ—λŠ” DB 에 μ‹€μ œλ‘œ λ°˜μ˜λ˜μ§€ μ•ŠλŠ”λ‹€. 즉, ν”ŒλŸ¬μ‹œλŠ” DB 와 동기화λ₯Ό ν•˜λŠ” 과정이라고 생각할 수 있으며, 컀밋 μ‹œμ μ— μ‹€μ œλ‘œ DB 에 λ°˜μ˜λœλ‹€.

ν”ŒλŸ¬μ‹œ λ°œμƒ

ν”ŒλŸ¬μ‹œλŠ” DB νŠΈλžœμž­μ…˜μ΄ 컀밋될 λ•Œ μžλ™μ μœΌλ‘œ λ°œμƒν•œλ‹€. μ΄λŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ κΈ°λŠ₯ 쀑 ν•˜λ‚˜μΈ λ³€κ²½ 감지λ₯Ό 톡해 μˆ˜μ •λœ μ—”ν‹°ν‹°μ˜ 정보λ₯Ό μ“°κΈ° 지연 SQL μ €μž₯μ†Œμ— μ €μž₯ν•΄λ‘”λ‹€. 이후, ν”Œλž˜μ‹œκ°€ λ°œμƒν•˜λ©΄ μ“°κΈ° 지연 SQL μ €μž₯μ†Œμ˜ 쿼리λ₯Ό DB 에 전솑

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό ν”ŒλŸ¬μ‹œν•˜λŠ” 방법

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό ν”ŒλŸ¬μ‹œν•˜λŠ” 방법은 총 3가지가 μ‘΄μž¬ν•œλ‹€.

  • em.flush() λ₯Ό ν†΅ν•œ 직접 호좜 (거의 μ‚¬μš©ν•˜μ§€ μ•ŠμŒ)
  • νŠΈλžœμž­μ…˜ 컀밋을 ν†΅ν•œ μžλ™ 호좜
  • JPQL 쿼리 싀행을 ν†΅ν•œ μžλ™ 호좜 ν”ŒλŸ¬μ‹œλ₯Ό ν•˜λ©΄ 1μ°¨ μΊμ‹œκ°€ μ§€μ›Œμ§€λŠ” 것은 μ•„λ‹ˆκ³ , μ“°κΈ° 지연 SQL μ €μž₯μ†Œμ— μžˆλŠ” 쿼리가 DB 에 λ™κΈ°ν™”λœλ‹€λŠ” 의미둜 생각할 수 μžˆλ‹€.

JPQL 쿼리 μ‹€ν–‰ μ‹œ ν”ŒλŸ¬μ‹œκ°€ μžλ™μœΌλ‘œ ν˜ΈμΆœλ˜λŠ” 이유

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
// 쀑간에 JPQL μ‹€ν–‰
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μžˆλŠ” 데이터λ₯Ό JPQL 쿼리둜 μ‘°νšŒν•˜λ €κ³  ν•˜λ©΄ DB 에 μ—†κΈ° λ•Œλ¬Έμ— λ¬Έμ œκ°€ 될 수 μžˆλ‹€. κ·Έλž˜μ„œ JPQL 쿼리 μ‹€ν–‰ μ‹œ μžλ™μœΌλ‘œ ν”ŒλŸ¬μ‹œ λ˜λ„λ‘ μ„€μ •λ˜μ–΄ μžˆλ‹€.

ν”ŒλŸ¬μ‹œ λͺ¨λ“œ μ˜΅μ…˜

em.setFlushMode(FlushModeType.AUTO);
// μ»€λ°‹μ΄λ‚˜ 쿼리λ₯Ό μ‹€ν–‰ν•  λ•Œ ν”ŒλŸ¬μ‹œ (κΈ°λ³Έκ°’)
em.setFlushMode(FlushModeType.COMMIT);
// 컀밋할 λ•Œλ§Œ ν”ŒλŸ¬μ‹œ, 크게 도움이 λ˜μ§€ μ•ŠμŒ, κ·Έλƒ₯ μ˜€ν† λ₯Ό μ“°λŠ” 것이 μ’‹μŒ

ν”ŒλŸ¬μ‹œλŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ λ³€κ²½λ‚΄μš©μ„ DB 에 λ™κΈ°ν™”ν•˜λŠ” 것일 뿐, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό λΉ„μš°μ§€ μ•ŠλŠ”λ‹€. νŠΈλžœμž­μ…˜μ΄λΌλŠ” μž‘μ—… λ‹¨μœ„κ°€ μ€‘μš”ν•˜κΈ° λ•Œλ¬Έμ—, 컀밋 μ§μ „μ—λ§Œ ν”ŒλŸ¬μ‹œλ₯Ό 톡해 동기화 ν•΄μ£Όλ©΄ λœλ‹€.

μ€€μ˜μ† μƒνƒœ


μ€€μ˜μ† μƒνƒœλž€, μ˜μ† μƒνƒœμ˜ μ—”ν‹°ν‹°κ°€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ 뢄리(detached)λ˜λŠ” 것을 μ˜λ―Έν•œλ‹€. μ€€μ˜μ† μƒνƒœκ°€ 된 μ—”ν‹°ν‹°λŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ μ œκ³΅ν•˜λŠ” Dirty-Checking κ³Ό 같은 κΈ°λŠ₯을 μ‚¬μš©ν•˜μ§€ λͺ»ν•œλ‹€. em.persist() λ‚˜ em.find() λ₯Ό μ‚¬μš©ν•˜λ©΄ μ—”ν‹°ν‹°κ°€ μ˜μ† μƒνƒœλ‘œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— λ“±λ‘λœλ‹€.

μ€€μ˜μ† μƒνƒœλ‘œ λ§Œλ“œλŠ” 방법

  • em.detach(entity); λ₯Ό 톡해 νŠΉμ • μ—”ν‹°ν‹°λ§Œ μ€€μ˜μ† μƒνƒœλ‘œ μ „ν™˜
  • em.clear(); λ₯Ό 톡해 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ™„μ „νžˆ μ΄ˆκΈ°ν™”
  • em.close(); λ₯Ό 톡해 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ’…λ£Œ