refactor: 초기 더미 데이터 생성 처리 로직 수정

This commit is contained in:
이상진 2025-10-04 17:46:18 +09:00
parent 9bc5b50a8f
commit f04d521029

View File

@ -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<Long> = transactionExecutionUtil.withNewTransaction(isReadOnly = true) {
val stores: List<StoreEntity> = 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<Array<Any>>()
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<Pair<Long, Long>> = getStoreWithManagers()
val themes: List<Triple<Long, Short, LocalDateTime>> = getThemes()
val maxAvailableMinutes = themes.maxOf { it.second.toInt() }
val scheduleCountPerDay = 5
val themes: List<ThemeEntity> = getThemes()
val maxScheduleCountPerDay = 10
val startTime = LocalTime.of(10, 0)
var lastTime = startTime
val times = mutableListOf<LocalTime>()
repeat(scheduleCountPerDay) {
times.add(lastTime)
lastTime = lastTime.plusMinutes(maxAvailableMinutes.toLong() + 10L)
val themeWithTimes: Map<ThemeEntity, List<LocalTime>> = themes.associateWith { theme ->
val times = mutableListOf<LocalTime>()
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<Pair<Long, Long>> = getStoreWithManagers()
// val availableThemes: List<ThemeEntity> = 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<Long, Short, LocalDateTime>,
stores: List<Pair<Long, Long>>,
times: List<LocalTime>
store: Pair<Long, Long>,
themeWithTimes: Map<ThemeEntity, List<LocalTime>>
) {
val sql = """
INSERT INTO schedule (
@ -457,24 +483,22 @@ class ScheduleDataInitializer : AbstractDataInitializer() {
val batchArgs = mutableListOf<Array<Any>>()
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<Triple<Long, Short, LocalDateTime>> {
private fun getThemes(): List<ThemeEntity> {
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<List<ScheduleWithThemeParticipants>> = 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<PaymentWithMethods> = 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