diff --git a/common/persistence/src/main/kotlin/com/sangdol/common/persistence/AuditingBaseEntity.kt b/common/persistence/src/main/kotlin/com/sangdol/common/persistence/AuditingBaseEntity.kt index a9fca2c7..82b2d41f 100644 --- a/common/persistence/src/main/kotlin/com/sangdol/common/persistence/AuditingBaseEntity.kt +++ b/common/persistence/src/main/kotlin/com/sangdol/common/persistence/AuditingBaseEntity.kt @@ -8,7 +8,7 @@ import org.springframework.data.annotation.CreatedDate import org.springframework.data.annotation.LastModifiedBy import org.springframework.data.annotation.LastModifiedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener -import java.time.LocalDateTime +import java.time.Instant @MappedSuperclass @EntityListeners(AuditingEntityListener::class) @@ -17,7 +17,7 @@ abstract class AuditingBaseEntity( ) : PersistableBaseEntity(id) { @Column(updatable = false) @CreatedDate - lateinit var createdAt: LocalDateTime + lateinit var createdAt: Instant @Column(updatable = false) @CreatedBy @@ -25,7 +25,7 @@ abstract class AuditingBaseEntity( @Column @LastModifiedDate - lateinit var updatedAt: LocalDateTime + lateinit var updatedAt: Instant @Column @LastModifiedBy diff --git a/service/src/main/kotlin/com/sangdol/roomescape/auth/infrastructure/persistence/LoginHistoryEntity.kt b/service/src/main/kotlin/com/sangdol/roomescape/auth/infrastructure/persistence/LoginHistoryEntity.kt index 8d8b2e67..86bcb168 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/auth/infrastructure/persistence/LoginHistoryEntity.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/auth/infrastructure/persistence/LoginHistoryEntity.kt @@ -5,7 +5,7 @@ import com.sangdol.roomescape.auth.web.PrincipalType import jakarta.persistence.* import org.springframework.data.annotation.CreatedDate import org.springframework.data.jpa.domain.support.AuditingEntityListener -import java.time.LocalDateTime +import java.time.Instant @Entity @Table(name = "login_history") @@ -24,5 +24,5 @@ class LoginHistoryEntity( @Column(updatable = false) @CreatedDate - var createdAt: LocalDateTime? = null, + var createdAt: Instant? = null, ) : PersistableBaseEntity(id) diff --git a/service/src/main/kotlin/com/sangdol/roomescape/common/types/AuditingInfo.kt b/service/src/main/kotlin/com/sangdol/roomescape/common/types/AuditingInfo.kt index 83693c60..94cc0998 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/common/types/AuditingInfo.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/common/types/AuditingInfo.kt @@ -1,6 +1,6 @@ package com.sangdol.roomescape.common.types -import java.time.LocalDateTime +import java.time.Instant data class Auditor( val id: Long, @@ -12,8 +12,8 @@ data class Auditor( } data class AuditingInfo( - val createdAt: LocalDateTime, + val createdAt: Instant, val createdBy: Auditor, - val updatedAt: LocalDateTime, + val updatedAt: Instant, val updatedBy: Auditor, ) diff --git a/service/src/main/kotlin/com/sangdol/roomescape/reservation/business/ReservationService.kt b/service/src/main/kotlin/com/sangdol/roomescape/reservation/business/ReservationService.kt index 98b645e8..315e1b6f 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/reservation/business/ReservationService.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/reservation/business/ReservationService.kt @@ -19,7 +19,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging import org.springframework.data.repository.findByIdOrNull import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional -import java.time.LocalDateTime +import java.time.Instant private val log: KLogger = KotlinLogging.logger {} @@ -145,7 +145,7 @@ class ReservationService( reservationId = reservation.id, canceledBy = user.id, cancelReason = cancelReason, - canceledAt = LocalDateTime.now(), + canceledAt = Instant.now(), status = CanceledReservationStatus.COMPLETED ).also { canceledReservationRepository.save(it) diff --git a/service/src/main/kotlin/com/sangdol/roomescape/reservation/business/scheduler/IncompletedReservationScheduler.kt b/service/src/main/kotlin/com/sangdol/roomescape/reservation/business/scheduler/IncompletedReservationScheduler.kt index b15876b1..01edf4ad 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/reservation/business/scheduler/IncompletedReservationScheduler.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/reservation/business/scheduler/IncompletedReservationScheduler.kt @@ -9,7 +9,7 @@ import org.springframework.scheduling.annotation.EnableScheduling import org.springframework.scheduling.annotation.Scheduled import org.springframework.stereotype.Component import org.springframework.transaction.annotation.Transactional -import java.time.LocalDateTime +import java.time.Instant import java.util.concurrent.TimeUnit private val log: KLogger = KotlinLogging.logger {} @@ -26,7 +26,7 @@ class IncompletedReservationScheduler( fun processExpiredHoldSchedule() { log.info { "[IncompletedReservationScheduler] 만료 시간이 지난 ${ScheduleStatus.HOLD} 상태의 일정 재활성화 시작" } - scheduleRepository.releaseExpiredHolds(LocalDateTime.now()).also { + scheduleRepository.releaseExpiredHolds(Instant.now()).also { log.info { "[IncompletedReservationScheduler] ${it}개의 일정 재활성화 완료" } } } @@ -36,7 +36,7 @@ class IncompletedReservationScheduler( fun processExpiredReservation() { log.info { "[IncompletedReservationScheduler] 결제되지 않은 예약 만료 처리 시작 " } - reservationRepository.expirePendingReservations(LocalDateTime.now()).also { + reservationRepository.expirePendingReservations(Instant.now()).also { log.info { "[IncompletedReservationScheduler] ${it}개의 예약 및 일정 처리 완료" } } } diff --git a/service/src/main/kotlin/com/sangdol/roomescape/reservation/infrastructure/persistence/CanceledReservationEntity.kt b/service/src/main/kotlin/com/sangdol/roomescape/reservation/infrastructure/persistence/CanceledReservationEntity.kt index 81832831..ab12c7bc 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/reservation/infrastructure/persistence/CanceledReservationEntity.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/reservation/infrastructure/persistence/CanceledReservationEntity.kt @@ -5,7 +5,7 @@ import jakarta.persistence.Entity import jakarta.persistence.EnumType import jakarta.persistence.Enumerated import jakarta.persistence.Table -import java.time.LocalDateTime +import java.time.Instant @Entity @Table(name = "canceled_reservation") @@ -15,7 +15,7 @@ class CanceledReservationEntity( val reservationId: Long, val canceledBy: Long, val cancelReason: String, - val canceledAt: LocalDateTime, + val canceledAt: Instant, @Enumerated(value = EnumType.STRING) val status: CanceledReservationStatus, diff --git a/service/src/main/kotlin/com/sangdol/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt b/service/src/main/kotlin/com/sangdol/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt index 9ef52754..ef5fcc80 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/reservation/infrastructure/persistence/ReservationRepository.kt @@ -4,7 +4,7 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param -import java.time.LocalDateTime +import java.time.Instant interface ReservationRepository : JpaRepository { @@ -24,5 +24,5 @@ interface ReservationRepository : JpaRepository { WHERE r.status = 'PENDING' AND r.created_at <= DATE_SUB(CURRENT_TIMESTAMP, INTERVAL 5 MINUTE) """, nativeQuery = true) - fun expirePendingReservations(@Param("now") now: LocalDateTime): Int + fun expirePendingReservations(@Param("now") now: Instant): Int } diff --git a/service/src/main/kotlin/com/sangdol/roomescape/reservation/web/ReservationDto.kt b/service/src/main/kotlin/com/sangdol/roomescape/reservation/web/ReservationDto.kt index 2349b81b..6a950ff6 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/reservation/web/ReservationDto.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/reservation/web/ReservationDto.kt @@ -1,13 +1,13 @@ package com.sangdol.roomescape.reservation.web -import jakarta.validation.constraints.NotEmpty import com.sangdol.roomescape.payment.web.PaymentWithDetailResponse import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationEntity import com.sangdol.roomescape.reservation.infrastructure.persistence.ReservationStatus import com.sangdol.roomescape.schedule.web.ScheduleOverviewResponse import com.sangdol.roomescape.user.web.UserContactResponse +import jakarta.validation.constraints.NotEmpty +import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime import java.time.LocalTime data class PendingReservationCreateRequest( @@ -79,7 +79,7 @@ data class ReservationDetailResponse( val id: Long, val reserver: ReserverInfo, val user: UserContactResponse, - val applicationDateTime: LocalDateTime, + val applicationDateTime: Instant, val payment: PaymentWithDetailResponse?, ) diff --git a/service/src/main/kotlin/com/sangdol/roomescape/schedule/infrastructure/persistence/ScheduleEntity.kt b/service/src/main/kotlin/com/sangdol/roomescape/schedule/infrastructure/persistence/ScheduleEntity.kt index bd6f2a6c..770a4592 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/schedule/infrastructure/persistence/ScheduleEntity.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/schedule/infrastructure/persistence/ScheduleEntity.kt @@ -3,8 +3,8 @@ package com.sangdol.roomescape.schedule.infrastructure.persistence import com.sangdol.common.persistence.AuditingBaseEntity import jakarta.persistence.* import org.springframework.data.jpa.domain.support.AuditingEntityListener +import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime import java.time.LocalTime @Entity @@ -20,7 +20,7 @@ class ScheduleEntity( @Enumerated(value = EnumType.STRING) var status: ScheduleStatus, - var holdExpiredAt: LocalDateTime? = null + var holdExpiredAt: Instant? = null ) : AuditingBaseEntity(id) { fun modifyIfNotNull( time: LocalTime?, diff --git a/service/src/main/kotlin/com/sangdol/roomescape/schedule/infrastructure/persistence/ScheduleRepository.kt b/service/src/main/kotlin/com/sangdol/roomescape/schedule/infrastructure/persistence/ScheduleRepository.kt index a30fbbed..623c564b 100644 --- a/service/src/main/kotlin/com/sangdol/roomescape/schedule/infrastructure/persistence/ScheduleRepository.kt +++ b/service/src/main/kotlin/com/sangdol/roomescape/schedule/infrastructure/persistence/ScheduleRepository.kt @@ -7,8 +7,8 @@ import org.springframework.data.jpa.repository.Lock import org.springframework.data.jpa.repository.Modifying import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.query.Param +import java.time.Instant import java.time.LocalDate -import java.time.LocalDateTime import java.time.LocalTime interface ScheduleRepository : JpaRepository { @@ -108,7 +108,7 @@ interface ScheduleRepository : JpaRepository { s.status = :changeStatus, s.holdExpiredAt = CASE WHEN :changeStatus = com.sangdol.roomescape.schedule.infrastructure.persistence.ScheduleStatus.HOLD - THEN CURRENT_TIMESTAMP + 5 MINUTE + THEN :expiredAt ELSE NULL END WHERE @@ -117,7 +117,7 @@ interface ScheduleRepository : JpaRepository { s.status = :currentStatus """ ) - fun changeStatus(id: Long, currentStatus: ScheduleStatus, changeStatus: ScheduleStatus): Int + fun changeStatus(id: Long, currentStatus: ScheduleStatus, changeStatus: ScheduleStatus, expiredAt: Instant = Instant.now().plusSeconds(5 * 60)): Int @Modifying @Query( @@ -137,5 +137,5 @@ interface ScheduleRepository : JpaRepository { ) """ ) - fun releaseExpiredHolds(@Param("now") now: LocalDateTime): Int + fun releaseExpiredHolds(@Param("now") now: Instant): Int } diff --git a/service/src/test/kotlin/com/sangdol/data/DefaultDataInitializer.kt b/service/src/test/kotlin/com/sangdol/data/DefaultDataInitializer.kt index 9c6b7d24..8ee9b99f 100644 --- a/service/src/test/kotlin/com/sangdol/data/DefaultDataInitializer.kt +++ b/service/src/test/kotlin/com/sangdol/data/DefaultDataInitializer.kt @@ -22,19 +22,16 @@ import com.sangdol.roomescape.user.infrastructure.persistence.UserStatus import com.sangdol.roomescape.user.web.UserContactResponse 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.* import kotlinx.coroutines.sync.Semaphore import org.springframework.beans.factory.annotation.Autowired import org.springframework.jdbc.core.JdbcTemplate import org.springframework.test.context.ActiveProfiles import java.sql.Timestamp +import java.time.Instant import java.time.LocalDateTime import java.time.LocalTime -import java.time.OffsetDateTime +import java.time.ZoneId @ActiveProfiles("test", "data") abstract class AbstractDataInitializer( @@ -327,7 +324,7 @@ class UserDataInitializer : AbstractDataInitializer() { } while (true) user.phone = newPhone - user.updatedAt = LocalDateTime.now() + user.updatedAt = Instant.now() entityManager.merge(user) } } @@ -488,14 +485,17 @@ class ScheduleDataInitializer : AbstractDataInitializer() { themeWithTimes.forEach { (theme, times) -> val themeCreatedAt = theme.createdAt (1..3).forEach { - val date = themeCreatedAt.toLocalDate().plusDays(it.toLong()) + val themeCreatedDateTime = themeCreatedAt.atZone(ZoneId.systemDefault()) + val themeCreatedDate = themeCreatedDateTime.toLocalDate().plusDays(it.toLong()) + val themeCreatedTime = themeCreatedDateTime.toLocalTime() + times.forEach { time -> val storeId = store.first val storeAdminId = store.second batchArgs.add( arrayOf( - idGenerator.create(), storeId, theme.id, date, time, - status, storeAdminId, storeAdminId, themeCreatedAt.plusHours(1), themeCreatedAt.plusHours(1) + idGenerator.create(), storeId, theme.id, themeCreatedDate, time, + status, storeAdminId, storeAdminId, themeCreatedTime.plusHours(1), themeCreatedTime.plusHours(1) ) ) diff --git a/service/src/test/kotlin/com/sangdol/data/PopulationDataParser.kt b/service/src/test/kotlin/com/sangdol/data/PopulationDataParser.kt index 516490ca..cfd6d592 100644 --- a/service/src/test/kotlin/com/sangdol/data/PopulationDataParser.kt +++ b/service/src/test/kotlin/com/sangdol/data/PopulationDataParser.kt @@ -195,8 +195,7 @@ private fun randomLocalDateTime(): String { return LocalDateTime.of(year, month, day, hour, minute, second) .atZone(ZoneId.systemDefault()) - .toOffsetDateTime() - .format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX")) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS")) } private fun generateBusinessRegNum(): String { diff --git a/service/src/test/kotlin/com/sangdol/roomescape/reservation/IncompletedReservationSchedulerTest.kt b/service/src/test/kotlin/com/sangdol/roomescape/reservation/IncompletedReservationSchedulerTest.kt index f71a240c..c317525f 100644 --- a/service/src/test/kotlin/com/sangdol/roomescape/reservation/IncompletedReservationSchedulerTest.kt +++ b/service/src/test/kotlin/com/sangdol/roomescape/reservation/IncompletedReservationSchedulerTest.kt @@ -13,7 +13,8 @@ import io.kotest.assertions.assertSoftly import io.kotest.matchers.shouldBe import org.springframework.data.repository.findByIdOrNull import org.springframework.jdbc.core.JdbcTemplate -import java.time.LocalDateTime +import java.time.Instant +import java.time.temporal.ChronoUnit /** * @see com.sangdol.roomescape.reservation.business.scheduler.IncompletedReservationScheduler @@ -31,7 +32,7 @@ class IncompletedReservationSchedulerTest( test("예약이 없고, hold_expired_at 시간이 지난 ${ScheduleStatus.HOLD} 일정을 ${ScheduleStatus.AVAILABLE} 상태로 바꾼다.") { val schedule: ScheduleEntity = dummyInitializer.createSchedule().apply { this.status = ScheduleStatus.HOLD - this.holdExpiredAt = LocalDateTime.now().minusSeconds(1) + this.holdExpiredAt = Instant.now().minusSeconds(1) }.also { scheduleRepository.saveAndFlush(it) } @@ -52,16 +53,13 @@ class IncompletedReservationSchedulerTest( jdbcTemplate.execute("UPDATE reservation SET created_at = DATE_SUB(NOW(), INTERVAL 5 MINUTE) WHERE id = ${it.id}") } - val now = LocalDateTime.now() - transactionExecutionUtil.withNewTransaction(isReadOnly = false) { incompletedReservationScheduler.processExpiredReservation() } assertSoftly(reservationRepository.findByIdOrNull(reservation.id)!!) { this.status shouldBe ReservationStatus.EXPIRED - this.updatedAt.hour shouldBe now.hour - this.updatedAt.minute shouldBe now.minute + this.updatedAt.truncatedTo(ChronoUnit.MINUTES) shouldBe Instant.now().truncatedTo(ChronoUnit.MINUTES) } assertSoftly(scheduleRepository.findByIdOrNull(reservation.scheduleId)!!) { diff --git a/service/src/test/kotlin/com/sangdol/roomescape/reservation/ReservationConcurrencyTest.kt b/service/src/test/kotlin/com/sangdol/roomescape/reservation/ReservationConcurrencyTest.kt index 111c14e1..75fc7279 100644 --- a/service/src/test/kotlin/com/sangdol/roomescape/reservation/ReservationConcurrencyTest.kt +++ b/service/src/test/kotlin/com/sangdol/roomescape/reservation/ReservationConcurrencyTest.kt @@ -20,7 +20,7 @@ import kotlinx.coroutines.withContext import org.springframework.data.repository.findByIdOrNull import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.support.TransactionTemplate -import java.time.LocalDateTime +import java.time.Instant class ReservationConcurrencyTest( private val transactionManager: PlatformTransactionManager, @@ -35,7 +35,7 @@ class ReservationConcurrencyTest( val user = testAuthUtil.defaultUserLogin().first val schedule = dummyInitializer.createSchedule().also { it.status = ScheduleStatus.HOLD - it.holdExpiredAt = LocalDateTime.now().minusMinutes(1) + it.holdExpiredAt = Instant.now().minusSeconds(1 * 60) scheduleRepository.save(it) } lateinit var response: PendingReservationCreateResponse