Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Comment thread
eastshine2741 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@ import com.wafflestudio.snuttev.core.domain.lecture.model.SnuttLectureIdMap
import com.wafflestudio.snuttev.core.domain.lecture.repository.LectureRepository
import com.wafflestudio.snuttev.core.domain.lecture.repository.SemesterLectureRepository
import com.wafflestudio.snuttev.core.domain.lecture.repository.SnuttLectureIdMapRepository
import com.wafflestudio.snuttev.core.domain.tag.repository.TagGroupRepository
import com.wafflestudio.snuttev.core.domain.tag.repository.TagRepository
import com.wafflestudio.snuttev.core.domain.tag.service.TagService
import com.wafflestudio.snuttev.sync.model.SnuttSemesterLecture
import jakarta.persistence.EntityManagerFactory
import org.springframework.batch.core.ExitStatus
import org.springframework.batch.core.job.Job
import org.springframework.batch.core.job.builder.JobBuilder
import org.springframework.batch.core.listener.StepExecutionListener
import org.springframework.batch.core.repository.JobRepository
import org.springframework.batch.core.step.Step
import org.springframework.batch.core.step.StepExecution
import org.springframework.batch.core.step.builder.StepBuilder
import org.springframework.batch.infrastructure.item.ItemProcessor
import org.springframework.batch.infrastructure.item.ItemWriter
Expand All @@ -37,7 +43,10 @@ class SnuttLectureSyncJobConfig(
private val semesterLectureRepository: SemesterLectureRepository,
private val lectureRepository: LectureRepository,
private val snuttLectureIdMapRepository: SnuttLectureIdMapRepository,
private val tagRepository: TagRepository,
private val tagGroupRepository: TagGroupRepository,
private val ratingSyncJob: Job,
private val tagService: TagService,
) {
companion object {
private const val JOB_NAME = "SYNC_JOB"
Expand Down Expand Up @@ -84,6 +93,7 @@ class SnuttLectureSyncJobConfig(
.and("semester")
.isEqualTo(targetSemester),
),
TargetYearSemester(targetYear, targetSemester),
),
).next(ratingSyncJobStep(jobRepository))
.build()
Expand Down Expand Up @@ -111,6 +121,7 @@ class SnuttLectureSyncJobConfig(
private fun customReaderStep(
jobRepository: JobRepository,
query: Query,
targetYearSemester: TargetYearSemester? = null,
): Step =
StepBuilder(CUSTOM_READER_JOB_STEP, jobRepository)
.chunk<SnuttSemesterLecture, SyncProcessResult>(CHUNK_SIZE)
Expand All @@ -121,6 +132,7 @@ class SnuttLectureSyncJobConfig(
).reader(reader(query))
.processor(processor())
.writer(writer())
.listener(tagSaveStepListener(targetYearSemester))
.build()

private fun reader(query: Query): MongoCursorItemReader<SnuttSemesterLecture> =
Expand Down Expand Up @@ -187,6 +199,17 @@ class SnuttLectureSyncJobConfig(
snuttLectureIdMapRepository.saveAll(items.map { it.snuttLectureIdMap })
}

private fun tagSaveStepListener(targetYearSemester: TargetYearSemester? = null): StepExecutionListener {
return object : StepExecutionListener {
override fun afterStep(stepExecution: StepExecution): ExitStatus {
if (stepExecution.exitStatus == ExitStatus.COMPLETED && targetYearSemester != null) {
tagService.saveYearSemesterTagIfNotExists(targetYearSemester.year, targetYearSemester.semester)
Copy link
Copy Markdown
Author

@jdahl0711 jdahl0711 May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

save tag 로직을 tagSerivce로 옮겨서 @transcactional로 묶었습니다

}
return stepExecution.exitStatus
}
}
}

private fun ratingSyncJobStep(jobRepository: JobRepository): Step =
StepBuilder(SnuttRatingSyncJobConfig.RATING_SYNC_JOB_NAME, jobRepository)
.job(ratingSyncJob)
Expand All @@ -198,3 +221,8 @@ data class SyncProcessResult(
val semesterLecture: SemesterLecture,
val snuttLectureIdMap: SnuttLectureIdMap,
)

data class TargetYearSemester(
val year: Int,
val semester: Int,
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ import java.time.LocalDateTime
open class BaseEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
open val id: Long? = null,
@Column(name = "created_at", nullable = false)
open val createdAt: LocalDateTime = LocalDateTime.now(),
open var id: Long? = null,
@Column(name = "created_at", nullable = false, updatable = false)
open var createdAt: LocalDateTime = LocalDateTime.now(),
@field:UpdateTimestamp
@Column(name = "updated_at", nullable = false)
@OptimisticLock(excluded = true)
open val updatedAt: LocalDateTime? = LocalDateTime.now(),
open var updatedAt: LocalDateTime? = LocalDateTime.now(),
Comment on lines +16 to +22
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BaseEntity가 수정된 이유가 뭔가용?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed: java.lang.NoSuchMethodError: 'void com.wafflestudio.snuttev.core.common.model.BaseEntity.$$_hibernate_write_id(java.lang.Long)'] with root cause

처음에 이렇게 에러 떠서 찾아보니까 BaseEntity id가 val이라 write method를 작성할 수가 없는게 원인이더라고요. 그래서 var로 바꾸니까 제대로 동작했습니다.

createdAt이랑 updatedAt도 원래는 val이었는데 충돌 가능성을 낮추기 위해 var로 교체하고, 대신 createdAt은 처음 db에 추가한 이후에 바꾸지 못하도록 updatable=false로 설정했습니다.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 잘 이해를 못 했는데, 이번에 작성해주신 코드에서 Tag 객체가 생성된 후 id, createdAt, updatedAt을 바꾸는 코드는 없었지만 hibernate에서 에러를 낸 건가용...?
그렇다면 저희가 기존에 엔티티를 수정하고 save하는 경우에도 같은 에러가 나야 했을 것 같은데 이번에만 난 이유도 궁금합니다!

)
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@ package com.wafflestudio.snuttev.core.common.type

enum class Semester(
val value: Int,
val label: String,
) {
SPRING(1),
SUMMER(2),
AUTUMN(3),
WINTER(4),
SPRING(1, "1"),
SUMMER(2, "여름"),
AUTUMN(3, "2"),
WINTER(4, "겨울"),
;

companion object {
fun labelOfOrNull(value: Int): String? = entries.firstOrNull { it.value == value }?.label
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invalid semester value일 경우(학기 숫자가 1~4가 아닌 값이 들어올 때) 기존에는 exception을 띄웠었는데, 그냥 로그만 남기고, 리스너에서 tag 저장 작업 중에 리턴하는거로 수정했습니다

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,15 @@ import org.springframework.data.jpa.repository.Query
interface TagRepository : JpaRepository<Tag, Long> {
@Query("SELECT t FROM Tag t JOIN FETCH t.tagGroup WHERE t.id IN :tagIdList")
fun getTagsWithTagGroupByTagsIdIsIn(tagIdList: List<Long>): List<Tag>

fun searchTagByStringValue(stringValue: String): Tag?

@Query(
"""
SELECT COALESCE(MIN(t.ordering), 0)
FROM Tag t
WHERE t.tagGroup.id = :tagGroupId
""",
)
fun findMinOrderingByTagGroupId(tagGroupId: Long): Int
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.wafflestudio.snuttev.core.domain.tag.service

import com.wafflestudio.snuttev.core.common.error.TagGroupNotFoundException
import com.wafflestudio.snuttev.core.common.type.Semester
import com.wafflestudio.snuttev.core.common.util.cache.Cache
import com.wafflestudio.snuttev.core.common.util.cache.CacheKey
import com.wafflestudio.snuttev.core.domain.tag.dto.SearchTagResponse
Expand All @@ -9,14 +10,21 @@ import com.wafflestudio.snuttev.core.domain.tag.dto.TagGroupDto
import com.wafflestudio.snuttev.core.domain.tag.model.Tag
import com.wafflestudio.snuttev.core.domain.tag.model.TagGroup
import com.wafflestudio.snuttev.core.domain.tag.repository.TagGroupRepository
import com.wafflestudio.snuttev.core.domain.tag.repository.TagRepository
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

@Service
class TagService internal constructor(
private val tagGroupRepository: TagGroupRepository,
private val tagRepository: TagRepository,
private val cache: Cache,
) {
companion object {
private val log = LoggerFactory.getLogger(TagService::class.java)
}

@Transactional(readOnly = true)
fun getMainTags(): TagGroupDto =
cache.withCache(CacheKey.MAIN_TAGS.build()) {
Expand Down Expand Up @@ -53,4 +61,38 @@ class TagService internal constructor(
description = tag.description,
ordering = tag.ordering,
)

Copy link
Copy Markdown
Author

@jdahl0711 jdahl0711 May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SnuttLectureSyncJob Config에서 TagService로 옮겨온 로직

변경사항
-"학기"가 tag group db에 없거나, 입력된 학기 정보가 잘못된 경우에 대한 대응
기존: 각각 custom exception / 변경: log 남기고 tag 저장 작업 중단 후 리턴 (앞서 진행된 sync 작업은 정상 진행됨)

@Transactional
fun saveYearSemesterTagIfNotExists(
year: Int,
semester: Int,
) {
val stringValue = "$year,$semester"
if (tagRepository.searchTagByStringValue(stringValue) == null) {
val tagGroup =
tagGroupRepository.findByName(name = "학기") ?: run {
log.warn("TagGroup not found. name={}", "학기")
return
}

val semesterName =
Semester.labelOfOrNull(semester) ?: run {
log.warn("Invalid semester value. semester={}", semester)
return
}

val name =
year.toString() + " " + semesterName + "학기"
val ordering = -1 + tagRepository.findMinOrderingByTagGroupId(tagGroup.id!!)
tagRepository.save(
Tag(
tagGroup = tagGroup,
name = name,
ordering = ordering,
stringValue = stringValue,
description = null,
),
)
}
}
}
Loading