generated from pricelees/issue-pr-template
[#46] 더미 데이터 생성 및 1개의 슬로우쿼리 개선 #47
@ -9,11 +9,13 @@ import org.springframework.transaction.annotation.Transactional
|
|||||||
import roomescape.admin.business.AdminService
|
import roomescape.admin.business.AdminService
|
||||||
import roomescape.common.config.next
|
import roomescape.common.config.next
|
||||||
import roomescape.common.dto.AuditInfo
|
import roomescape.common.dto.AuditInfo
|
||||||
|
import roomescape.common.util.DateUtils
|
||||||
import roomescape.theme.exception.ThemeErrorCode
|
import roomescape.theme.exception.ThemeErrorCode
|
||||||
import roomescape.theme.exception.ThemeException
|
import roomescape.theme.exception.ThemeException
|
||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
import roomescape.theme.infrastructure.persistence.ThemeRepository
|
import roomescape.theme.infrastructure.persistence.ThemeRepository
|
||||||
import roomescape.theme.web.*
|
import roomescape.theme.web.*
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
private val log: KLogger = KotlinLogging.logger {}
|
private val log: KLogger = KotlinLogging.logger {}
|
||||||
|
|
||||||
@ -43,13 +45,18 @@ class ThemeService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional(readOnly = true)
|
@Transactional(readOnly = true)
|
||||||
fun findAllInfosByIds(request: ThemeIdListRequest): ThemeInfoListResponse {
|
fun findMostReservedThemeLastWeek(count: Int): ThemeInfoListResponse {
|
||||||
log.info { "[ThemeService.findThemesByIds] 예약 페이지에서의 테마 목록 조회 시작: themeIds=${request.themeIds}" }
|
log.info { "[ThemeService.findMostReservedThemeLastWeek] 인기 테마 조회 시작: count=$count" }
|
||||||
val result: List<ThemeEntity> = themeRepository.findAllByIdIn(request.themeIds)
|
|
||||||
|
val previousWeekSunday = DateUtils.getSundayOfPreviousWeek(LocalDate.now())
|
||||||
|
val previousWeekSaturday = previousWeekSunday.plusDays(6)
|
||||||
|
|
||||||
|
return themeRepository.findMostReservedThemeByDateAndCount(previousWeekSunday, previousWeekSaturday, count)
|
||||||
|
.toListResponse()
|
||||||
|
.also {
|
||||||
|
log.info { "[ThemeService.findMostReservedThemeLastWeek] ${it.themes.size} / $count 개의 인기 테마 조회 완료" }
|
||||||
|
}
|
||||||
|
|
||||||
return result.toInfoListResponse().also {
|
|
||||||
log.info { "[ThemeService.findThemesByIds] ${it.themes.size} / ${request.themeIds.size} 개 테마 조회 완료" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|||||||
@ -5,8 +5,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
|
|||||||
import io.swagger.v3.oas.annotations.responses.ApiResponses
|
import io.swagger.v3.oas.annotations.responses.ApiResponses
|
||||||
import jakarta.validation.Valid
|
import jakarta.validation.Valid
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping
|
||||||
import org.springframework.web.bind.annotation.PathVariable
|
import org.springframework.web.bind.annotation.PathVariable
|
||||||
import org.springframework.web.bind.annotation.RequestBody
|
import org.springframework.web.bind.annotation.RequestBody
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam
|
||||||
import roomescape.admin.infrastructure.persistence.AdminType
|
import roomescape.admin.infrastructure.persistence.AdminType
|
||||||
import roomescape.admin.infrastructure.persistence.Privilege
|
import roomescape.admin.infrastructure.persistence.Privilege
|
||||||
import roomescape.auth.web.support.AdminOnly
|
import roomescape.auth.web.support.AdminOnly
|
||||||
@ -50,13 +52,13 @@ interface AdminThemeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface PublicThemeAPI {
|
interface PublicThemeAPI {
|
||||||
@Public
|
|
||||||
@Operation(summary = "입력된 모든 ID에 대한 테마 정보 조회")
|
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true))
|
|
||||||
fun findThemeInfosByIds(request: ThemeIdListRequest): ResponseEntity<CommonApiResponse<ThemeInfoListResponse>>
|
|
||||||
|
|
||||||
@Public
|
@Public
|
||||||
@Operation(summary = "입력된 테마 ID에 대한 정보 조회")
|
@Operation(summary = "입력된 테마 ID에 대한 정보 조회")
|
||||||
@ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true))
|
@ApiResponses(ApiResponse(responseCode = "200", useReturnTypeSchema = true))
|
||||||
fun findThemeInfoById(@PathVariable id: Long): ResponseEntity<CommonApiResponse<ThemeInfoResponse>>
|
fun findThemeInfoById(@PathVariable id: Long): ResponseEntity<CommonApiResponse<ThemeInfoResponse>>
|
||||||
|
|
||||||
|
@Public
|
||||||
|
@Operation(summary = "지난 주에 가장 많이 예약된 count 개의 테마 조회")
|
||||||
|
@GetMapping("/most-reserved")
|
||||||
|
fun findMostReservedThemeLastWeek(@RequestParam count: Int): ResponseEntity<CommonApiResponse<ThemeInfoListResponse>>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package roomescape.theme.infrastructure.persistence
|
|||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository
|
import org.springframework.data.jpa.repository.JpaRepository
|
||||||
import org.springframework.data.jpa.repository.Query
|
import org.springframework.data.jpa.repository.Query
|
||||||
|
import roomescape.theme.business.domain.ThemeInfo
|
||||||
|
import java.time.LocalDate
|
||||||
|
|
||||||
interface ThemeRepository : JpaRepository<ThemeEntity, Long> {
|
interface ThemeRepository : JpaRepository<ThemeEntity, Long> {
|
||||||
|
|
||||||
@ -10,5 +12,32 @@ interface ThemeRepository : JpaRepository<ThemeEntity, Long> {
|
|||||||
|
|
||||||
fun existsByName(name: String): Boolean
|
fun existsByName(name: String): Boolean
|
||||||
|
|
||||||
fun findAllByIdIn(themeIds: List<Long>): List<ThemeEntity>
|
@Query(
|
||||||
|
value = """
|
||||||
|
SELECT
|
||||||
|
t.id, t.name, t.description, t.difficulty, t.thumbnail_url, t.price,
|
||||||
|
t.min_participants, t.max_participants,
|
||||||
|
t.available_minutes, t.expected_minutes_from, t.expected_minutes_to
|
||||||
|
FROM
|
||||||
|
theme t
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
s.theme_id, count(*) as reservation_count
|
||||||
|
FROM
|
||||||
|
schedule s
|
||||||
|
JOIN
|
||||||
|
reservation r ON s.id = r.schedule_id AND r.status = 'CONFIRMED'
|
||||||
|
WHERE
|
||||||
|
s.status = 'RESERVED'
|
||||||
|
AND (s.date BETWEEN :startFrom AND :endAt)
|
||||||
|
GROUP BY
|
||||||
|
s.theme_id
|
||||||
|
ORDER BY
|
||||||
|
reservation_count desc
|
||||||
|
LIMIT :count
|
||||||
|
) ranked_themes ON t.id = ranked_themes.theme_id
|
||||||
|
""",
|
||||||
|
nativeQuery = true
|
||||||
|
)
|
||||||
|
fun findMostReservedThemeByDateAndCount(startFrom: LocalDate, endAt: LocalDate, count: Int): List<ThemeInfo>
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
package roomescape.theme.web
|
package roomescape.theme.web
|
||||||
|
|
||||||
import jakarta.validation.Valid
|
|
||||||
import org.springframework.http.ResponseEntity
|
import org.springframework.http.ResponseEntity
|
||||||
import org.springframework.web.bind.annotation.*
|
import org.springframework.web.bind.annotation.*
|
||||||
import roomescape.common.dto.response.CommonApiResponse
|
import roomescape.common.dto.response.CommonApiResponse
|
||||||
@ -12,15 +11,6 @@ import roomescape.theme.docs.PublicThemeAPI
|
|||||||
class ThemeController(
|
class ThemeController(
|
||||||
private val themeService: ThemeService,
|
private val themeService: ThemeService,
|
||||||
) : PublicThemeAPI {
|
) : PublicThemeAPI {
|
||||||
@PostMapping("/batch")
|
|
||||||
override fun findThemeInfosByIds(
|
|
||||||
@Valid @RequestBody request: ThemeIdListRequest
|
|
||||||
): ResponseEntity<CommonApiResponse<ThemeInfoListResponse>> {
|
|
||||||
val response = themeService.findAllInfosByIds(request)
|
|
||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/{id}")
|
@GetMapping("/{id}")
|
||||||
override fun findThemeInfoById(
|
override fun findThemeInfoById(
|
||||||
@PathVariable id: Long
|
@PathVariable id: Long
|
||||||
@ -29,4 +19,13 @@ class ThemeController(
|
|||||||
|
|
||||||
return ResponseEntity.ok(CommonApiResponse(response))
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/most-reserved")
|
||||||
|
override fun findMostReservedThemeLastWeek(
|
||||||
|
@RequestParam count: Int
|
||||||
|
): ResponseEntity<CommonApiResponse<ThemeInfoListResponse>> {
|
||||||
|
val response = themeService.findMostReservedThemeLastWeek(count)
|
||||||
|
|
||||||
|
return ResponseEntity.ok(CommonApiResponse(response))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package roomescape.theme.web
|
package roomescape.theme.web
|
||||||
|
|
||||||
import roomescape.theme.infrastructure.persistence.Difficulty
|
import roomescape.theme.business.domain.ThemeInfo
|
||||||
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
import roomescape.theme.infrastructure.persistence.ThemeEntity
|
||||||
|
|
||||||
data class ThemeIdListRequest(
|
data class ThemeIdListRequest(
|
||||||
@ -12,7 +12,7 @@ data class ThemeInfoResponse(
|
|||||||
val name: String,
|
val name: String,
|
||||||
val thumbnailUrl: String,
|
val thumbnailUrl: String,
|
||||||
val description: String,
|
val description: String,
|
||||||
val difficulty: Difficulty,
|
val difficulty: String,
|
||||||
val price: Int,
|
val price: Int,
|
||||||
val minParticipants: Short,
|
val minParticipants: Short,
|
||||||
val maxParticipants: Short,
|
val maxParticipants: Short,
|
||||||
@ -21,7 +21,7 @@ data class ThemeInfoResponse(
|
|||||||
val expectedMinutesTo: Short
|
val expectedMinutesTo: Short
|
||||||
)
|
)
|
||||||
|
|
||||||
fun ThemeEntity.toInfoResponse() = ThemeInfoResponse(
|
fun ThemeInfo.toInfoResponse() = ThemeInfoResponse(
|
||||||
id = this.id,
|
id = this.id,
|
||||||
name = this.name,
|
name = this.name,
|
||||||
thumbnailUrl = this.thumbnailUrl,
|
thumbnailUrl = this.thumbnailUrl,
|
||||||
@ -35,10 +35,24 @@ fun ThemeEntity.toInfoResponse() = ThemeInfoResponse(
|
|||||||
expectedMinutesTo = this.expectedMinutesTo
|
expectedMinutesTo = this.expectedMinutesTo
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun ThemeEntity.toInfoResponse() = ThemeInfoResponse(
|
||||||
|
id = this.id,
|
||||||
|
name = this.name,
|
||||||
|
thumbnailUrl = this.thumbnailUrl,
|
||||||
|
description = this.description,
|
||||||
|
difficulty = this.difficulty.name,
|
||||||
|
price = this.price,
|
||||||
|
minParticipants = this.minParticipants,
|
||||||
|
maxParticipants = this.maxParticipants,
|
||||||
|
availableMinutes = this.availableMinutes,
|
||||||
|
expectedMinutesFrom = this.expectedMinutesFrom,
|
||||||
|
expectedMinutesTo = this.expectedMinutesTo
|
||||||
|
)
|
||||||
|
|
||||||
data class ThemeInfoListResponse(
|
data class ThemeInfoListResponse(
|
||||||
val themes: List<ThemeInfoResponse>
|
val themes: List<ThemeInfoResponse>
|
||||||
)
|
)
|
||||||
|
|
||||||
fun List<ThemeEntity>.toInfoListResponse() = ThemeInfoListResponse(
|
fun List<ThemeInfo>.toListResponse() = ThemeInfoListResponse(
|
||||||
themes = this.map { it.toInfoResponse() }
|
themes = this.map { it.toInfoResponse() }
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user