λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ“— JPA

[Spring Data JPA] Transaction 없이 읽기

by GroovyArea 2023. 1. 30.

νšŒμ‚¬μ— μž…μ‚¬ν•΄μ„œ μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄λ˜ 쀑에
λΆ„λͺ…νžˆ jpa entity 객체의 읽기 κ³Όμ • 쀑, λΆ„λͺ…νžˆ νŠΈλžœμž­μ…˜ μ²˜λ¦¬κ°€ ν•„μš”ν•œ μ½”λ“œκ°€ λ³΄μ˜€λ‹€.

이λ₯Όν…Œλ©΄ Spring Security 인증을 거치고 Security Context Holder 에 μ €μž₯된 UserPrinciple 객체

@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : user")
annotation class CurrentUser(val require: Boolean = true)

μ΄λŸ¬ν•œ μ• λ…Έν…Œμ΄μ…˜μ„ ν”νžˆλ“€ μ „μ—­μ μœΌλ‘œ Api μ—μ„œ νŒŒλΌλ―Έν„°λ‘œ μ£Όμž… λ°›μ•„μ„œ μ‚¬μš©ν•œλ‹€.

이λ₯Ό ν…Œλ©΄,

@GetMapping
fun getProject(
	@CurrentUser user : User
) : ProjectResponse {
	//...
}

μ΄λŸ¬ν•œ user 객체λ₯Ό κ°€μ§€κ³  μ„œλΉ„μŠ€ λ‹¨μ—μ„œ μ§€μ—° λ‘œλ”©μœΌλ‘œ getterλ₯Ό 톡해 μΆ”κ°€ ν”„λ‘œνΌν‹°λ₯Ό μ‘°νšŒν•  λ•Œ μ‚¬μš©ν–ˆλ‹€.
λ‚˜λŠ” ν•΄λ‹Ή 객체가 μ€€μ˜μ† μƒνƒœλΌκ³  μƒκ°ν–ˆμ—ˆλ‹€. λ‹Ήμ—°νžˆ userμ—μ„œ μ‘°νšŒν•  λ•Œλ„ @Transactional 이 ν•„μš”ν•  거라고 μƒκ°ν–ˆλ‹€.

근데 μ΄μƒν•˜κ²Œλ„ νŠΈλžœμž­μ…˜ 없이 ν”„λ‘œνΌν‹°λ“€μ„ getter둜 κ°€μ Έμ˜¬ 수 μžˆμ—ˆλ‹€.

μ‹œκ°„μ„ λ“€μ—¬ jpa κ°œλ…μ„ μ°Ύμ•„λ³΄λ˜ 도쀑, λ‚΄κ°€ 잊고 있던 κ°œλ…μ΄ ν•˜λ‚˜ μƒκ°λ‚˜, μ •λ¦¬ν•˜κ²Œ λ˜μ—ˆλ‹€.

Spring Data JPA 의 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ μ „λž΅

μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ μ „λž΅μ€ νŠΈλžœμž­μ…˜μ˜ λ²”μœ„μ™€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ˜ 생쑴 λ²”μœ„κ°€ κ°™μŒμ„ 이λ₯΄λŠ” μ „λž΅μ΄λ‹€.

νŠΈλžœμž­μ…˜μ΄ μ‹œμž‘ 되면, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ 생성 되고,
νŠΈλžœμž­μ…˜μ΄ μ’…λ£Œ 되면, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈκ°€ μ’…λ£Œ λœλ‹€.

κ·Έλž˜μ„œ 보톡 @Transactional 을 λΆ™μ—¬ ν•¨μˆ˜λ₯Ό μ„ μ–Έν•œλ‹€.
이 말은 νŠΈλžœμž­μ…˜ λ²”μœ„ λ°–μ˜ μ»¨νŠΈλ‘€λŸ¬λ‚˜ λ·° 단은 μ€€μ˜μ† μƒνƒœκ°€ λœλ‹€.
ν•΄μ„œ, νŠΈλžœμž­μ…˜ λ²”μœ„ λ°–μ—μ„œλŠ” λ³€κ²½ 감지가 μΌμ–΄λ‚˜μ§€ μ•ŠλŠ”λ‹€.

νŠΈλžœμž­μ…˜ λ²”μœ„ λ°–μ—μ„œ μ§€μ—° λ‘œλ”©μ„ μ‚¬μš©ν•  κ²½μš°λŠ”, entityκ°€ μ€€μ˜μ† μƒνƒœμ΄λ―€λ‘œ,
LazyInitializationException μ˜ˆμ™Έκ°€ λ°œμƒν•  것이닀.

이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄,

  1. ν•„μš”ν•œ μ—”ν‹°ν‹°λ₯Ό 미리 μ¦‰μ‹œλ‘œλ”© ν•˜λŠ” 방법,
  2. OSIV λ₯Ό 켜 놓고 μ—”ν‹°ν‹°λ₯Ό μ˜μ† μƒνƒœλ‘œ μœ μ§€ν•˜λŠ” 방법 등이 μžˆλ‹€.

첫 번째 방법은
Global Fetch μ „λž΅μ„ Eager둜 μ„€μ •ν•˜λŠ” 방법인데,
λ„ˆλ¬΄ 단점이 크닀.
λ°”λ‘œ N+1 λ¬Έμ œκ°€ λ°œμƒν•œλ‹€λŠ” κ²ƒμœΌλ‘œ μ§€μ–‘ν•œλ‹€.

두 번째 방법은
μ˜μ† μƒνƒœλ₯Ό μœ μ§€ν•˜κΈ° μœ„ν•΄,
DB μ™€μ˜ 컀λ„₯μ…˜μ„ 계속 μœ μ§€ν•˜κ²Œ λ˜μ–΄ νŠΈλž˜ν”½μ΄ 많이 λͺ°λ¦¬λŠ” μ„œλΉ„μŠ€μ˜ 경우 μ„±λŠ₯이 μ €ν•˜λ  수 μžˆλ‹€.

νŠΈλžœμž­μ…˜ 없이 읽기

λ‹¨μˆœνžˆ μ§€μ—° λ‘œλ”©μ„ 톡해 λ‹¨μˆœ 쑰회만 ν•  κ²½μš°μ—λŠ” @Transactional 을 μ„ μ–Έν•  ν•„μš”κ°€ μ—†λ‹€.

μœ„μ— 예제 μ½”λ“œμ—μ„œ User EntityλŠ” μ€€μ˜μ† μƒνƒœμ΄μ§€λ§Œ,
μ˜μ†μ„± μ»¨ν…μŠ€νŠΈ μžμ²΄κ°€ νŠΈλžœμž­μ…˜ λ²”μœ„ λ°–μ—μ„œλŠ” μ‘°νšŒκ°€ κ°€λŠ₯ν•˜λ―€λ‘œ,
getterλ₯Ό 톡해 ν”„λ‘œνΌν‹° μ‘°νšŒκ°€ κ°€λŠ₯ν•˜λ‹€.

fun getCard(user: User) : Card{
	return user.card
}

이런 μ‹μœΌλ‘œ user의 ν”„λ‘œνΌν‹°μΈ card λ₯Ό λ‹¨μˆœνžˆ μ½λŠ” 것을 νŠΈλžœμž­μ…˜ 없이 κ°€λŠ₯ν•˜λ‹€λŠ” μ–˜κΈ°μ΄λ‹€.

userλŠ” μ€€μ˜μ† μƒνƒœμ΄μ§€λ§Œ, μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλŠ” νŠΈλžœμž­μ…˜ λ²”μœ„ λ°–μ—μ„œλ„ 읽기가 κ°€λŠ₯ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

μ•žμœΌλ‘œ μ΄λŸ¬ν•œ μ „λž΅μ„ κΎΈμ€€νžˆ μ΄μš©ν•  수 μžˆμ„ 것 κ°™λ‹€.

λ°˜μ‘ν˜•