πŸ“• Backend/Spring Data JPA

[JPA] ν”„λ‘μ‹œ(Proxy)와 μ§€μ—°λ‘œλ”©(Lazy Loading)

Dongwoongkim 2023. 5. 4. 20:25

연관관계λ₯Ό κ°–λŠ” μ—¬λŸ¬ μ—”ν‹°ν‹°κ°€ μžˆμ„ λ•Œ ν•œ μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•  λ•Œ λ°˜λ“œμ‹œ μ—°κ΄€λœ 엔티티듀이 μ‚¬μš©λ˜μ§€λŠ” μ•ŠμŠ΅λ‹ˆλ‹€. 

 

λ³Έλ‘ λΆ€ν„° λ§ν•˜μžλ©΄, μ§€μ—° λ‘œλ”©μ„ μ‚¬μš©ν•˜λ©΄ Member엔티티와 Team μ—”ν‹°ν‹°κ°€ λ‹€λŒ€μΌλ‘œ λ§€ν•‘λ˜μ–΄ μžˆμ„ λ•Œ, Member Entityλ₯Ό μ‘°νšŒν•œλ‹€κ³  ν•΄μ„œ λ°˜λ“œμ‹œ Team EntityκΉŒμ§€ μ‘°νšŒλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 

 

JPAλŠ” μ—”ν‹°ν‹°κ°€ μ‹€μ œ μ‚¬μš©λ  λ•ŒκΉŒμ§€ λ°μ΄ν„°λ² μ΄μŠ€ 쑰회λ₯Ό μ§€μ—°ν•˜λŠ” 방법을 μ œκ³΅ν•©λ‹ˆλ‹€. 이λ₯Ό μ§€μ—° λ‘œλ”©(Lazy Loading)이

라고 ν•©λ‹ˆλ‹€.

  • μ‰½κ²Œ 말해 team.getName() 처럼 Team μ—”ν‹°ν‹°μ˜ 값을 μ‹€μ œ μ‚¬μš©ν•˜λŠ” μ‹œμ μ— Team Entity에 ν•„μš”ν•œ 데이터λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€.
  • μ§€μ—° λ‘œλ”© κΈ°λŠ₯은 μ‹€μ œ μ—”ν‹°ν‹° 객체 λŒ€μ‹ μ— λ°μ΄ν„°λ² μ΄μŠ€ 쑰회λ₯Ό μ§€μ—°ν•  수 μžˆλŠ” κ°€μ§œ ν”„λ‘μ‹œ 객체λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. ν”„λ‘μ‹œμ— λŒ€ν•΄ μ•Œμ•„λ΄…μ‹œλ‹€.

ν”„λ‘μ‹œ

μ—”ν‹°ν‹°λ₯Ό μ‹€μ œ μ‚¬μš©ν•˜λŠ” μ‹œμ κΉŒμ§€ λ°μ΄ν„°λ² μ΄μŠ€ 쑰회λ₯Ό 미루고 싢은 경우 EntityManager.getReference()λ₯Ό μ‚¬μš©ν•΄μ„œ ν”„λ‘μ‹œ 객체λ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

Member member = em.getReference(Member.class,1L);
  • memberλŠ” μ‹€μ œ 객체가 μ•„λ‹Œ ν”„λ‘μ‹œ κ°μ²΄μž…λ‹ˆλ‹€.
  • find() λ©”μ†Œλ“œμ™€ 달리 이 λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œ JPAλŠ” μ‹€μ œ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό μ‘°νšŒν•˜μ§€λ„, μ‹€μ œ μ—”ν‹°ν‹° 객체λ₯Ό μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ˜μ†ν•˜μ§€λ„ μ•ŠμŠ΅λ‹ˆλ‹€.
  • λŒ€μ‹  λ°μ΄ν„°λ² μ΄μŠ€ 접근을 μœ„μž„ν•œ ν”„λ‘μ‹œ 객체λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

쑰금 ν—·κ°ˆλ¦¬λ‹ˆκΉŒ 흐름을 ν•œ 번 짚고 λ„˜μ–΄κ°€κ² μŠ΅λ‹ˆλ‹€.

Member member = em.getReference(Member.class, 1L);

➑️ Member νƒ€μž… Proxy 객체 생성 (member)

// ν”„λ‘μ‹œ 객체 졜초 μ‚¬μš© -> ν”„λ‘μ‹œ 객체 μ΄ˆκΈ°ν™” μš”μ²­ 
member.getName();

// μ‹λ³„μžμ˜ 경우 μ΄ˆκΈ°ν™”λ₯Ό μš”μ²­ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λ’€μ—μ„œ μ„€λͺ…
member.getId();

ν”„λ‘μ‹œ 객체둜 μ‹€μ œ μ—”ν‹°ν‹° 객체의 λ©”μ†Œλ“œ 호좜

➑️ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—κ²Œ μ‹€μ œ μ—”ν‹°ν‹°λ₯Ό μš”μ²­ν•©λ‹ˆλ‹€. (μ΄ˆκΈ°ν™” μš”μ²­)

 

➑️ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μžˆλŠ” 경우, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” DBλ₯Ό μ‘°νšŒν•˜μ§€ μ•Šκ³  ν”„λ‘μ‹œκ°€ μ•„λ‹Œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ μ‹€μ œ Entityλ₯Ό   λ°˜ν™˜ν•©λ‹ˆλ‹€. (μ‹€μ œ Entity μ‚¬μš©)

쑰회 λŒ€μƒμ΄ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— 이미 있으면 ν”„λ‘μ‹œ 객체λ₯Ό μ‚¬μš©ν•  μ΄μœ κ°€ μ—†μœΌλ―€λ‘œ μ‹€μ œ μ—”ν‹°ν‹°λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

➑️ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ—†λŠ” 경우, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” DBλ₯Ό μ‘°νšŒν•΄μ„œ μ‹€μ œ Entityλ₯Ό ν”„λ‘μ‹œ 객체의 target에 λ¦¬ν„΄ν•©λ‹ˆλ‹€. (μ‹€μ œ μ—”ν‹°ν‹° 생성 및 μ°Έμ‘°λ₯Ό ν”„λ‘μ‹œμ— 보관)

 

➑️ target의 getId()λ₯Ό ν˜ΈμΆœν•΄μ„œ κ²°κ³Όλ₯Ό λ¦¬ν„΄ν•©λ‹ˆλ‹€. ( target.getId() 호좜 ) (ν”„λ‘μ‹œ 객체의 λ©”μ†Œλ“œ μ‹€ν–‰ -> μ‹€μ œ 객체의 λ©”μ†Œλ“œ μ‹€ν–‰) 

ν”„λ‘μ‹œμ˜ νŠΉμ§•

ν”„λ‘μ‹œ ν΄λž˜μŠ€λŠ” μ‹€μ œ 클래슀λ₯Ό 상속받아 λ§Œλ“€μ–΄μ§€λ―€λ‘œ μ‹€μ œ ν΄λž˜μŠ€μ™€ 겉λͺ¨μ–‘이 κ°™μŠ΅λ‹ˆλ‹€. μ‚¬μš©μžλŠ” 이 객체가 ν”„λ‘μ‹œ 객첸지, μ‹€μ œ μ—”ν‹°ν‹° 객체인지 κ΅¬λΆ„ν•˜μ§€ μ•Šμ•„λ„ λ‚΄λΆ€μ μœΌλ‘œ ν”„λ‘μ‹œλ‘œ μ²˜λ¦¬ν•˜κΈ° λ•Œλ¬Έμ— ꡬ뢄할 ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€.

  • ν”„λ‘μ‹œ κ°μ²΄λŠ” 처음 μƒμ„±μ‹œ ν•œ 번만 μ΄ˆκΈ°ν™”λ©λ‹ˆλ‹€.
  • ν”„λ‘μ‹œ 객체가 μ΄ˆκΈ°ν™” 되면 ν”„λ‘μ‹œ 객체λ₯Ό 톡해 (μ—„λ°€νžˆ λ§ν•˜λ©΄ 내뢀에 μ‹€μ œ 엔티티와 λ§€ν•‘λœ target을 톡해) μ‹€μ œ 엔티티에 μ ‘κ·Όν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ 도움을 λ°›μ•„μ•Ό κ°€λŠ₯ν•˜λ―€λ‘œ μ€€μ˜μ† μƒνƒœμ˜ μ—”ν‹°ν‹°λ₯Ό ν”„λ‘μ‹œ 객체둜 생성할 경우, μ˜ˆμ™Έκ°€ λ°œμƒν•©λ‹ˆλ‹€.

ν”„λ‘μ‹œμ™€ μ‹λ³„μž(PK)

μ—”ν‹°ν‹°λ₯Ό ν”„λ‘μ‹œλ‘œ μ‘°νšŒν•  λ•Œ μ‹λ³„μž(PK) 값을 νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬ν•˜λŠ”λ° ν”„λ‘μ‹œ κ°μ²΄λŠ” 이 μ‹λ³„μž 값을 λ³΄κ΄€ν•˜κ³  μžˆμœΌλ―€λ‘œ, μ‹λ³„μž 값을 μ‘°νšŒν•˜λŠ” team.getId()λ₯Ό ν˜ΈμΆœν•΄λ„ ν”„λ‘μ‹œλ₯Ό μ΄ˆκΈ°ν™” ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

Team team = em.getReference(Team.class, 1L);
team.getId(); // μ΄ˆκΈ°ν™” μš”μ²­ X
μ—”ν‹°ν‹° μ ‘κ·Ό 방식을 @Access(AccessType.PROPERTY) 둜 μ„€μ •ν•œ κ²½μš°μ—λ§Œ μ΄ˆκΈ°ν™” μš”μ²­ X
μ—”ν‹°ν‹° μ ‘κ·Ό 방식을 @Access(AccessType.FIELD) 둜 μ„€μ •ν•œ κ²½μš°μ—λŠ” μ΄ˆκΈ°ν™” μš”μ²­ O
Member member = em.find(Member.class, 1L);
Team team = em.getReference(Team.class, 1L); // SQL μ‹€ν–‰ X
member.setTeam(team);

ν”„λ‘μ‹œ κ°μ²΄λŠ” μ‹λ³„μž 값을 κ°€μ§€κ³  있기 떄문에 μ‹λ³„μž 값을 μ‚¬μš©ν•˜μ—¬ 연관관계λ₯Ό μ„€μ •ν•  λ•ŒλŠ” ν”„λ‘μ‹œλ₯Ό μ΄ˆκΈ°ν™”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•˜λ©΄ λ°μ΄ν„°λ² μ΄μŠ€ μ ‘κ·ΌνšŸμˆ˜λ₯Ό 쀄일 수 μžˆμŠ΅λ‹ˆλ‹€.

연관관계λ₯Ό μ„€μ •ν•  λ•ŒλŠ” μ—”ν‹°ν‹° μ ‘κ·Ό 방식을 ν•„λ“œλ‘œ 섀정해도 ν”„λ‘μ‹œλ₯Ό μ΄ˆκΈ°ν™”ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

 

ν”„λ‘μ‹œ μ΄ˆκΈ°ν™” 확인 
- JPAκ°€ μ œκ³΅ν•˜λŠ” PersistenceUnitUtil.isLoaded(Object entity) λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•˜λ©΄ ν”„λ‘μ‹œ μΈμŠ€ν„΄μŠ€μ˜ μ΄ˆκΈ°ν™” μ—¬λΆ€λ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.
boolean isLoad = em.getPersistenceUnitUtil().isLoaded(entity);

ν”„λ‘μ‹œ/μ‹€μ œ 객체 확인
System.out.println("memberProxy = " + member.getClass().getName());
-> ν”„λ‘μ‹œ 객체의 경우 ...javassist..

μ§€μ—°λ‘œλ”©, μ¦‰μ‹œλ‘œλ”© 

ν”„λ‘μ‹œ κ°μ²΄λŠ” 주둜 μ—°κ΄€λœ μ—”ν‹°ν‹°λ₯Ό μ§€μ—°λ‘œλ”©(μ‹€μ œ μ—”ν‹°ν‹°λ₯Ό μ‚¬μš©ν•˜λŠ” κ²½μš°μ—λ§Œ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό 쑰회)ν•  λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€.

 

μ¦‰μ‹œλ‘œλ”©μ€ μ§€μ—°λ‘œλ”©μ˜ λ°˜λŒ€κ°œλ…μœΌλ‘œ μ‹€μ œ μ—”ν‹°ν‹°λ₯Ό μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” κ²½μš°μ—λ„ μ—”ν‹°ν‹°λ₯Ό μ‘°νšŒν•  λ•Œ μ—°κ΄€λœ μ—”ν‹°ν‹°λ₯Ό ν•¨κ»˜ μ‘°νšŒν•˜λŠ” λ°©μ‹μž…λ‹ˆλ‹€.

 

μ¦‰μ‹œλ‘œλ”© μ‚¬μš© : 닀쀑성 μ–΄λ…Έν…Œμ΄μ…˜μ— fetch 속성을 FetchType.EAGER둜 μ„€μ •

@Entity
public class Member {
	...
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
 	...   
}

μ‹€μ œ μ‹€ν–‰λ˜λŠ” SQL

select
    M.*,
    T.*
from Member M
	left outer join 
    Team T on 
	M.TEAM_ID = T.TEAM_ID 
where M.MEMBER_ID = 1L
  • μ¦‰μ‹œ λ‘œλ”©μ˜ 경우 μ¦‰μ‹œ λ‘œλ”©λ˜λŠ” 엔티티듀을 Joinν•˜λ©° ν•œλ²ˆμ˜ 쿼리λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€.
  • μ¦‰μ‹œ λ‘œλ”©μ˜ 경우 기본적으둜 λ‚΄λΆ€ 쑰인(쑰건에 λ§žλŠ” λ°μ΄ν„°λ§Œ μΆ”μΆœ ꡐ집합)이 μ•„λ‹Œ μ™ΈλΆ€ 쑰인(쑰건에 λ§žλŠ” 데이터든, 쑰건에 λ§žμ§€ μ•ŠλŠ”λ°μ΄ν„°λ“  ν•©μ§‘ν•©μœΌλ‘œ μΆ”μΆœ)ν•©λ‹ˆλ‹€.
    • 쑰건에 λ§žμ§€ μ•ŠλŠ” 경우 null κ°’μœΌλ‘œ 처리.
  • λ‚΄λΆ€ 쑰인이 μ™ΈλΆ€ 쑰인보닀 μ„±λŠ₯μ΄λ‚˜ μ΅œμ ν™”λ©΄μ—μ„œ μœ λ¦¬ν•˜κΈ° λ•Œλ¬Έμ— λ‚΄λΆ€ 쑰인을 μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ–΄λ–»κ²Œ?
    • @JoinColumn의 nullable 속성을 false둜 μ„€μ •ν•˜λ©΄ 내뢀쑰인을 μ‚¬μš©ν•©λ‹ˆλ‹€.

μ§€μ—°λ‘œλ”© μ‚¬μš© : 닀쀑성 μ–΄λ…Έν…Œμ΄μ…˜μ— fetch 속성을 FetcchType.LAZY둜 μ„€μ • 

@Entity
public class Member {
	...
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TEAM_ID")
    private Team team;
 	...   
}

 

Member member = em.find(Member.class, 1L);
  • Team은 μ§€μ—° λ‘œλ”©μ„ μ‚¬μš©ν–ˆκΈ° λ•Œλ¬Έμ— member만 μ‘°νšŒν•˜κ³  νŒ€μ€ μ‘°νšŒν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
  • λŒ€μ‹  team λ©€λ²„λ³€μˆ˜μ— ν”„λ‘μ‹œ 객체λ₯Ό λ„£μ–΄ λ‘‘λ‹ˆλ‹€!
Team team = member.getTeam(); // ν”„λ‘μ‹œ 객체!
  • team은 ν”„λ‘μ‹œ 객체!
team.getName(); // ν”„λ‘μ‹œ 객체 μ΄ˆκΈ°ν™” ν˜ΈμΆœν•΄μ„œ μ‹€μ œ νŒ€ 객체의 λ©”μ†Œλ“œ 호좜
  • μ΄λ ‡κ²Œ μ‹€μ œ 호좜이 μΌμ–΄λ‚˜λŠ” κ²½μš°μ—λ§Œ ν”„λ‘μ‹œ 객체λ₯Ό 톡해 μ‹€μ œ 객체λ₯Ό μ‚¬μš©ν•΄ λ‘œλ”©ν•©λ‹ˆλ‹€.
  • ν”„λ‘μ‹œ 객체 μ΄ˆκΈ°ν™” κ³Όμ •μ—μ„œ select * from team where team_id = ? SQL이 μ‹€ν–‰λ©λ‹ˆλ‹€. 

 

+μ§€μ—°λ‘œλ”©, μ¦‰μ‹œλ‘œλ”© κΈ°λ³Έ μ „λž΅

μ—°κ΄€λœ μ—”ν‹°ν‹°κ°€ ν•˜λ‚˜μΈ 경우 ( @ManyToOne, @OneToOne ) : μ¦‰μ‹œλ‘œλ”© μ‚¬μš©
μ—°κ΄€λœ μ—”ν‹°ν‹°κ°€ μ—¬λŸ¬κ°œμΈ 경우 ( @ManyToMany, @OneToMany ) : μ§€μ—°λ‘œλ”© μ‚¬μš©

μ—°κ΄€λ˜μ–΄ μžˆλŠ” μ—”ν‹°ν‹°κ°€ μ»¬λ ‰μ…˜μΈ 경우 μ„±λŠ₯μ €ν•˜ 이슈둜 μ¦‰μ‹œ λ‘œλ”©μ€ ꢌμž₯λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. 
+ μ»¬λ ‰μ…˜μ€ ν”„λ‘μ‹œ 객체가 μ•„λ‹Œ 'μ»¬λ ‰μ…˜ 래퍼'λΌλŠ” ν”„λ‘μ‹œκ°€ μ§€μ—°λ‘œλ”©μ„ μ²˜λ¦¬ν•΄μ€λ‹ˆλ‹€.

 

<정리>

  • μ§€μ—°λ‘œλ”© : μ—°κ΄€λœ μ—”ν‹°ν‹°λ₯Ό ν”„λ‘μ‹œλ‘œ 쑰회. ν”„λ‘μ‹œλ₯Ό μ‹€μ œ μ‚¬μš©ν•  λ•Œ μ΄ˆκΈ°ν™”ν•˜λ©΄μ„œ λ°μ΄ν„°λ² μ΄μŠ€λ₯Ό 쑰회
  • μ¦‰μ‹œλ‘œλ”© : μ—°κ΄€λœ μ—”ν‹°ν‹°λ₯Ό μ¦‰μ‹œ 쑰회. (SQL JOIN μ‚¬μš©)
    • μ¦‰μ‹œλ‘œλ”©μ€ 기본적으둜 항상 μ™ΈλΆ€ 쑰인을 μ‚¬μš©ν•˜κΈ° 떄문에 μ„±λŠ₯ μ΅œμ ν™”λ₯Ό μœ„ν•œ λ‚΄λΆ€ 쑰인을 μ‚¬μš©ν•˜κ³ μ‹ΆμœΌλ©΄ 닀쀑성 μ–΄λ…Έν…Œμ΄μ…˜μ— nullable 속성을 false둜 μ§€μ •ν•˜λ©΄ λ©λ‹ˆλ‹€.

<참고자료>