[#68] ArgumentResolver에서의 불필요한 DB 요청 로직 제거 (#69)

<!-- 제목 양식 -->
<!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) -->

## 📝 관련 이슈 및 PR

**PR과 관련된 이슈 번호**
- #68

##  작업 내용
<!-- 어떤 작업을 했는지 알려주세요! -->
- ArgumentResolver에서의 DB 조회 요청 제거

## 🧪 테스트
<!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! -->
- 성능 테스트 결과 Hikari Pending 커넥션의 최대 개수가 80 -> 5로 대폭 감소
- Tomcat 스레드도 기존은 최대 200개까지 활성화 되었으나, 개선 후 최대 80까지만 처리됨.
- P95 응답 시간 235 -> 141ms로 40% 개선

## 📚 참고 자료 및 기타
<!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! -->

Reviewed-on: #69
Co-authored-by: pricelees <priceelees@gmail.com>
Co-committed-by: pricelees <priceelees@gmail.com>
This commit is contained in:
이상진 2025-10-17 06:18:50 +00:00 committed by 이상진
parent 06f7faf7f9
commit be2e6c606e
7 changed files with 4 additions and 82 deletions

View File

@ -6,7 +6,7 @@ import com.sangdol.roomescape.auth.exception.AuthException
import com.sangdol.roomescape.auth.infrastructure.jwt.JwtUtils import com.sangdol.roomescape.auth.infrastructure.jwt.JwtUtils
import com.sangdol.roomescape.auth.web.support.User import com.sangdol.roomescape.auth.web.support.User
import com.sangdol.roomescape.auth.web.support.accessToken import com.sangdol.roomescape.auth.web.support.accessToken
import com.sangdol.roomescape.user.business.UserService import com.sangdol.roomescape.common.types.CurrentUserContext
import io.github.oshai.kotlinlogging.KLogger import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
@ -22,7 +22,6 @@ private val log: KLogger = KotlinLogging.logger {}
@Component @Component
class UserContextResolver( class UserContextResolver(
private val jwtUtils: JwtUtils, private val jwtUtils: JwtUtils,
private val userService: UserService,
) : HandlerMethodArgumentResolver { ) : HandlerMethodArgumentResolver {
override fun supportsParameter(parameter: MethodParameter): Boolean { override fun supportsParameter(parameter: MethodParameter): Boolean {
@ -43,7 +42,7 @@ class UserContextResolver(
MdcPrincipalIdUtil.set(it) MdcPrincipalIdUtil.set(it)
}.toLong() }.toLong()
return userService.findContextById(id) return CurrentUserContext(id = id)
} catch (e: Exception) { } catch (e: Exception) {
log.info { "[UserContextResolver] 회원 조회 실패. message=${e.message}" } log.info { "[UserContextResolver] 회원 조회 실패. message=${e.message}" }
throw AuthException(AuthErrorCode.MEMBER_NOT_FOUND) throw AuthException(AuthErrorCode.MEMBER_NOT_FOUND)

View File

@ -1,6 +1,5 @@
package com.sangdol.roomescape.common.types package com.sangdol.roomescape.common.types
data class CurrentUserContext( data class CurrentUserContext(
val id: Long, val id: Long
val name: String,
) )

View File

@ -1,7 +1,6 @@
package com.sangdol.roomescape.user.business package com.sangdol.roomescape.user.business
import com.sangdol.common.persistence.IDGenerator import com.sangdol.common.persistence.IDGenerator
import com.sangdol.roomescape.common.types.CurrentUserContext
import com.sangdol.roomescape.user.dto.UserContactResponse import com.sangdol.roomescape.user.dto.UserContactResponse
import com.sangdol.roomescape.user.dto.UserCreateRequest import com.sangdol.roomescape.user.dto.UserCreateRequest
import com.sangdol.roomescape.user.dto.UserCreateResponse import com.sangdol.roomescape.user.dto.UserCreateResponse
@ -28,17 +27,6 @@ class UserService(
private val userValidator: UserValidator, private val userValidator: UserValidator,
private val idGenerator: IDGenerator private val idGenerator: IDGenerator
) { ) {
@Transactional(readOnly = true)
fun findContextById(id: Long): CurrentUserContext {
log.info { "[findContextById] 현재 로그인된 회원 조회 시작: id=${id}" }
val user: UserEntity = findOrThrow(id)
return CurrentUserContext(user.id, user.name)
.also {
log.info { "[findContextById] 현재 로그인된 회원 조회 완료: id=${id}" }
}
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
fun findCredentialsByAccount(email: String): UserLoginCredentials { fun findCredentialsByAccount(email: String): UserLoginCredentials {
log.info { "[findCredentialsByAccount] 회원 조회 시작: email=${email}" } log.info { "[findCredentialsByAccount] 회원 조회 시작: email=${email}" }

View File

@ -43,16 +43,6 @@ class PaymentAPITest(
expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND
) )
} }
test("관리자") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin().second,
method = HttpMethod.POST,
endpoint = endpoint,
requestBody = PaymentFixture.cancelRequest,
expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND
)
}
} }
test("정상 취소") { test("정상 취소") {

View File

@ -49,15 +49,6 @@ class ReservationApiTest(
expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND
) )
} }
test("관리자") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin().second,
method = HttpMethod.POST,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND
)
}
} }
@ -185,15 +176,6 @@ class ReservationApiTest(
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
) )
} }
test("관리자") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin().second,
method = HttpMethod.POST,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
} }
test("정상 응답") { test("정상 응답") {
@ -238,15 +220,6 @@ class ReservationApiTest(
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
) )
} }
test("관리자") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin().second,
method = HttpMethod.POST,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
} }
test("정상 응답") { test("정상 응답") {
@ -315,15 +288,6 @@ class ReservationApiTest(
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
) )
} }
test("관리자") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin().second,
method = HttpMethod.POST,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
} }
test("정상 응답") { test("정상 응답") {
@ -377,15 +341,6 @@ class ReservationApiTest(
expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND expectedErrorCode = AuthErrorCode.TOKEN_NOT_FOUND
) )
} }
test("관리자") {
runExceptionTest(
token = testAuthUtil.defaultHqAdminLogin().second,
method = HttpMethod.POST,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.ACCESS_DENIED
)
}
} }
context("정상 응답") { context("정상 응답") {

View File

@ -108,7 +108,7 @@ class ReservationConcurrencyTest(
private fun createPendingReservation(user: UserEntity, schedule: ScheduleEntity): Long { private fun createPendingReservation(user: UserEntity, schedule: ScheduleEntity): Long {
return reservationService.createPendingReservation( return reservationService.createPendingReservation(
user = CurrentUserContext(id = user.id, name = user.name), user = CurrentUserContext(id = user.id),
request = PendingReservationCreateRequest( request = PendingReservationCreateRequest(
scheduleId = schedule.id, scheduleId = schedule.id,
reserverName = user.name, reserverName = user.name,

View File

@ -148,15 +148,6 @@ class UserApiTest(
expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND
) )
} }
test("관리자") {
runExceptionTest(
token = testAuthUtil.defaultStoreAdminLogin().second,
method = HttpMethod.GET,
endpoint = endpoint,
expectedErrorCode = AuthErrorCode.MEMBER_NOT_FOUND
)
}
} }
test("정상 응답") { test("정상 응답") {