From f04d52102985be6e11aaea2eaeebecd79deb2663 Mon Sep 17 00:00:00 2001 From: pricelees Date: Sat, 4 Oct 2025 17:46:18 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=B4=88=EA=B8=B0=20=EB=8D=94?= =?UTF-8?q?=EB=AF=B8=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sangdol/data/DefaultDataInitializer.kt | 148 +++++++++--------- 1 file changed, 71 insertions(+), 77 deletions(-) diff --git a/service/src/test/kotlin/com/sangdol/data/DefaultDataInitializer.kt b/service/src/test/kotlin/com/sangdol/data/DefaultDataInitializer.kt index aa1ac10e..345ea14e 100644 --- a/service/src/test/kotlin/com/sangdol/data/DefaultDataInitializer.kt +++ b/service/src/test/kotlin/com/sangdol/data/DefaultDataInitializer.kt @@ -8,11 +8,13 @@ import com.sangdol.roomescape.admin.infrastructure.persistence.AdminType import com.sangdol.roomescape.payment.infrastructure.common.* import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus import com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus +import com.sangdol.roomescape.store.infrastructure.persistence.StoreEntity import com.sangdol.roomescape.supports.AdminFixture import com.sangdol.roomescape.supports.FunSpecSpringbootTest import com.sangdol.roomescape.supports.randomPhoneNumber import com.sangdol.roomescape.supports.randomString import com.sangdol.roomescape.theme.infrastructure.persistence.Difficulty +import com.sangdol.roomescape.theme.infrastructure.persistence.ThemeEntity import com.sangdol.roomescape.user.business.SIGNUP import com.sangdol.roomescape.user.infrastructure.persistence.UserEntity import com.sangdol.roomescape.user.infrastructure.persistence.UserStatus @@ -21,6 +23,7 @@ import io.kotest.core.test.TestCaseOrder import jakarta.persistence.EntityManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore @@ -151,22 +154,22 @@ class DefaultDataInitializer : AbstractDataInitializer() { AdminPermissionLevel.READ_SUMMARY to 3 ) - val storeIds: List = transactionExecutionUtil.withNewTransaction(isReadOnly = true) { + val stores: List = transactionExecutionUtil.withNewTransaction(isReadOnly = true) { entityManager.createQuery( - "SELECT s.id FROM StoreEntity s", - Long::class.java + "SELECT s FROM StoreEntity s", + StoreEntity::class.java ).resultList - }!!.map { it as Long } + }!! transactionExecutionUtil.withNewTransaction(isReadOnly = false) { - storeIds.forEach { storeId -> + stores.forEach { store -> // StoreManager 1명 생성 val storeManager = AdminFixture.create( - account = "$storeId", + account = store.name, name = randomKoreanName(), phone = randomPhoneNumber(), type = AdminType.STORE, - storeId = storeId, + storeId = store.id, permissionLevel = AdminPermissionLevel.FULL_ACCESS ).apply { this.createdBy = superHQAdmin.id @@ -178,11 +181,11 @@ class DefaultDataInitializer : AbstractDataInitializer() { storeAdminCountsByPermissionLevel.forEach { (permissionLevel, count) -> repeat(count) { index -> AdminFixture.create( - account = randomString(), + account = "${store.name}-${permissionLevel.ordinal}${index}", name = randomKoreanName(), phone = randomPhoneNumber(), type = AdminType.STORE, - storeId = storeId, + storeId = store.id, permissionLevel = permissionLevel ).apply { this.createdBy = storeManager.id @@ -217,7 +220,7 @@ class DefaultDataInitializer : AbstractDataInitializer() { val batchArgs = mutableListOf>() repeat(500) { i -> - val randomDay = if (i <= 9) (1..30).random() else (1..365 * 2).random() + val randomDay = if (i <= 9) (7..30).random() else (30..365 * 2).random() val randomCreatedAt: LocalDateTime = LocalDateTime.now().minusDays(randomDay.toLong()) val randomThemeName = (1..7).random().let { repeat -> (1..repeat).joinToString("") { randomKoreanName() } } @@ -417,25 +420,49 @@ class UserDataInitializer : AbstractDataInitializer() { class ScheduleDataInitializer : AbstractDataInitializer() { init { context("일정 초기 데이터 생성") { - test("테마 생성일 기준으로 다음 3일차, 매일 5개의 일정을 모든 매장에 생성") { + test("테마 생성일 기준으로 다음 3일차, 매일 최대 10개의 일정을 모든 매장에 생성") { val stores: List> = getStoreWithManagers() - val themes: List> = getThemes() - val maxAvailableMinutes = themes.maxOf { it.second.toInt() } - val scheduleCountPerDay = 5 - + val themes: List = getThemes() + val maxScheduleCountPerDay = 10 val startTime = LocalTime.of(10, 0) - var lastTime = startTime - val times = mutableListOf() - repeat(scheduleCountPerDay) { - times.add(lastTime) - lastTime = lastTime.plusMinutes(maxAvailableMinutes.toLong() + 10L) + val themeWithTimes: Map> = themes.associateWith { theme -> + val times = mutableListOf() + val themeAvailableMinutes = theme.availableMinutes + var lastTime = startTime + + while (times.size <= maxScheduleCountPerDay && lastTime.hour in (10..23)) { + times.add(lastTime) + lastTime = lastTime.plusMinutes(themeAvailableMinutes + 10L) + } + + times } coroutineScope { - themes.forEach { theme -> + stores.map { store -> launch(Dispatchers.IO) { - processTheme(theme, stores, times) + processTheme(store, themeWithTimes) + } + } + } + } + + test("내일 ~ 일주일 뒤 까지의 일정 생성") { +// val stores: List> = getStoreWithManagers() +// val availableThemes: List = transactionExecutionUtil.withNewTransaction(isReadOnly = true) { +// entityManager.createQuery( +// "SELECT t FROM ThemeEntity t WHERE t.isActive = true AND t.createdAt >", ThemeEntity::class.java +// ).resultList +// }!!.take(10) + + coroutineScope { + val jobs = (1..100).map { i -> + launch(Dispatchers.IO) { + val threadName = Thread.currentThread().name + println("[$i] 시작: $threadName") + delay(1) + println("[$i] 완료: $threadName") } } } @@ -444,9 +471,8 @@ class ScheduleDataInitializer : AbstractDataInitializer() { } private suspend fun processTheme( - theme: Triple, - stores: List>, - times: List + store: Pair, + themeWithTimes: Map> ) { val sql = """ INSERT INTO schedule ( @@ -457,24 +483,22 @@ class ScheduleDataInitializer : AbstractDataInitializer() { val batchArgs = mutableListOf>() - val now = LocalDateTime.now() - stores.forEach { (storeId, adminId) -> - (1..3).forEach { dayOffset -> - val date = theme.third.toLocalDate().plusDays(dayOffset.toLong()) - + val status = ScheduleStatus.RESERVED.name + themeWithTimes.forEach { (theme, times) -> + val themeCreatedAt = theme.createdAt + (1..3).forEach { + val date = themeCreatedAt.toLocalDate().plusDays(it.toLong()) times.forEach { time -> - val scheduledAt = LocalDateTime.of(date, time) - val status = - if (scheduledAt.isAfter(now)) ScheduleStatus.AVAILABLE.name else ScheduleStatus.RESERVED.name - + val storeId = store.first + val storeAdminId = store.second batchArgs.add( arrayOf( - idGenerator.create(), storeId, theme.first, date, time, - status, adminId, adminId, Timestamp.valueOf(now), Timestamp.valueOf(now) + idGenerator.create(), storeId, theme.id, date, time, + status, storeAdminId, storeAdminId, themeCreatedAt.plusHours(1), themeCreatedAt.plusHours(1) ) ) - if (batchArgs.size >= 500) { + if (batchArgs.size >= 300) { executeBatch(sql, batchArgs).also { batchArgs.clear() } } } @@ -500,17 +524,13 @@ class ScheduleDataInitializer : AbstractDataInitializer() { } } - private fun getThemes(): List> { + private fun getThemes(): List { return transactionExecutionUtil.withNewTransaction(isReadOnly = true) { entityManager.createQuery( - "SELECT t._id, t.availableMinutes, t.createdAt FROM ThemeEntity t", - List::class.java - ) - .resultList - }!!.map { - val array = it as List<*> - Triple(array[0] as Long, array[1] as Short, array[2] as LocalDateTime) - } + "SELECT t FROM ThemeEntity t", + ThemeEntity::class.java + ).resultList + }!! } } @@ -528,10 +548,10 @@ class ReservationDataInitializer : AbstractDataInitializer() { init { context("예약 초기 데이터 생성") { test("${ScheduleStatus.RESERVED}인 모든 일정에 예약을 1개씩 배정한다.") { - val chunkSize = 10_000 + val chunkSize = 500 val chunkedSchedules: List> = entityManager.createQuery( - "SELECT new com.sangdol.data.ScheduleWithThemeParticipants(s._id, t.minParticipants, t.maxParticipants) FROM ScheduleEntity s JOIN ThemeEntity t ON s.themeId = t.id WHERE s.status = :status", + "SELECT new com.sangdol.roomescape.data.ScheduleWithThemeParticipants(s._id, t.minParticipants, t.maxParticipants) FROM ScheduleEntity s JOIN ThemeEntity t ON s.themeId = t.id WHERE s.status = :status", ScheduleWithThemeParticipants::class.java ).setParameter("status", ScheduleStatus.RESERVED).resultList.chunked(chunkSize) @@ -587,10 +607,6 @@ class ReservationDataInitializer : AbstractDataInitializer() { user.id, ) ) - - if (batchArgs.size >= 1_000) { - executeBatch(sql, batchArgs).also { batchArgs.clear() } - } } if (batchArgs.isNotEmpty()) executeBatch(sql, batchArgs).also { batchArgs.clear() } @@ -671,7 +687,7 @@ class PaymentDataInitializer : AbstractDataInitializer() { } coroutineScope { - allReservations.chunked(10_000).forEach { reservations -> + allReservations.chunked(500).forEach { reservations -> launch(Dispatchers.IO) { processPaymentAndDefaultDetail(reservations) } @@ -681,12 +697,12 @@ class PaymentDataInitializer : AbstractDataInitializer() { test("기존 결제 데이터에 상세 정보(계좌이체, 카드, 간편결제) 데이터를 생성한다.") { val allPayments: List = entityManager.createQuery( - "SELECT new com.sangdol.data.PaymentWithMethods(pd._id, p.totalAmount, p.method) FROM PaymentEntity p JOIN PaymentDetailEntity pd ON p._id = pd.paymentId", + "SELECT new com.sangdol.roomescape.data.PaymentWithMethods(pd._id, p.totalAmount, p.method) FROM PaymentEntity p JOIN PaymentDetailEntity pd ON p._id = pd.paymentId", PaymentWithMethods::class.java ).resultList coroutineScope { - allPayments.chunked(10_000).forEach { payments -> + allPayments.chunked(500).forEach { payments -> launch(Dispatchers.IO) { processPaymentDetail(payments) } @@ -731,9 +747,6 @@ class PaymentDataInitializer : AbstractDataInitializer() { approvedAtCache, ) ) - if (paymentBatchArgs.size >= 1_000) { - executeBatch(paymentSql, paymentBatchArgs).also { paymentBatchArgs.clear() } - } val suppliedAmount: Int = (totalPrice * 0.9).toInt() val vat: Int = (totalPrice - suppliedAmount) @@ -746,10 +759,6 @@ class PaymentDataInitializer : AbstractDataInitializer() { vat ) ) - - if (detailBatchArgs.size >= 1_000) { - executeBatch(paymentDetailSql, detailBatchArgs).also { detailBatchArgs.clear() } - } } if (paymentBatchArgs.isNotEmpty()) { @@ -780,9 +789,6 @@ class PaymentDataInitializer : AbstractDataInitializer() { settlementStatus ) ) - if (transferBatchArgs.size >= 1_000) { - executeBatch(paymentBankTransferDetailSql, transferBatchArgs).also { transferBatchArgs.clear() } - } } PaymentMethod.EASY_PAY -> { @@ -803,10 +809,6 @@ class PaymentDataInitializer : AbstractDataInitializer() { ) ) - if (cardBatchArgs.size >= 1_000) { - executeBatch(paymentCardDetailSql, cardBatchArgs).also { cardBatchArgs.clear() } - } - } else { easypayPrepaidBatchArgs.add( arrayOf( @@ -816,10 +818,6 @@ class PaymentDataInitializer : AbstractDataInitializer() { randomDiscountAmount, ) ) - - if (easypayPrepaidBatchArgs.size >= 1_000) { - executeBatch(paymentEasypayPrepaidDetailSql, easypayPrepaidBatchArgs).also { easypayPrepaidBatchArgs.clear() } - } } } @@ -839,10 +837,6 @@ class PaymentDataInitializer : AbstractDataInitializer() { 0, ) ) - - if (cardBatchArgs.size >= 1_000) { - executeBatch(paymentCardDetailSql, cardBatchArgs).also { cardBatchArgs.clear() } - } } else -> return@forEach