refactor: 모든 LocalDateTime, OffsetDateTime 타입 Instant 전환

This commit is contained in:
이상진 2025-10-05 22:03:41 +09:00
parent 2fa4874ad7
commit e93d8de6cc
14 changed files with 43 additions and 46 deletions

View File

@ -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

View File

@ -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)

View File

@ -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,
)

View File

@ -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)

View File

@ -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}개의 예약 및 일정 처리 완료" }
}
}

View File

@ -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,

View File

@ -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<ReservationEntity, Long> {
@ -24,5 +24,5 @@ interface ReservationRepository : JpaRepository<ReservationEntity, Long> {
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
}

View File

@ -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?,
)

View File

@ -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?,

View File

@ -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<ScheduleEntity, Long> {
@ -108,7 +108,7 @@ interface ScheduleRepository : JpaRepository<ScheduleEntity, Long> {
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<ScheduleEntity, Long> {
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<ScheduleEntity, Long> {
)
"""
)
fun releaseExpiredHolds(@Param("now") now: LocalDateTime): Int
fun releaseExpiredHolds(@Param("now") now: Instant): Int
}

View File

@ -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)
)
)

View File

@ -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 {

View File

@ -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)!!) {

View File

@ -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