Skip to content

Commit

Permalink
snu4t에 강의 평점 동기화 (#111)
Browse files Browse the repository at this point in the history
* LectureRepository에 findAllRatings 추가

* evInfo 동기화 Batch 추가

* 강의평 추가/삭제/수정 시 snu4t에 정보 적용

* add reactive mongodb dependency

* JpaPagingItemReader 사용

* MongoService 분리

* processor 없이 처리

* 필요없는 job 삭제
  • Loading branch information
asp345 authored Aug 27, 2024
1 parent 0ef5796 commit cc9c988
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class SnuttLectureSyncJobConfig(
private val semesterLectureRepository: SemesterLectureRepository,
private val lectureRepository: LectureRepository,
private val snuttLectureIdMapRepository: SnuttLectureIdMapRepository,
private val ratingSyncJob: Job,
) {
companion object {
private const val JOB_NAME = "SYNC_JOB"
Expand Down Expand Up @@ -78,6 +79,7 @@ class SnuttLectureSyncJobConfig(
),
),
)
.next(ratingSyncJobStep(jobRepository))
.build()
}

Expand All @@ -93,6 +95,7 @@ class SnuttLectureSyncJobConfig(
.toMutableMap()
return JobBuilder(JOB_NAME, jobRepository)
.start(customReaderStep(jobRepository, Query()))
.next(ratingSyncJobStep(jobRepository))
.build()
}

Expand Down Expand Up @@ -169,6 +172,11 @@ class SnuttLectureSyncJobConfig(
snuttLectureIdMapRepository.saveAll(items.map { it.snuttLectureIdMap })
}
}

private fun ratingSyncJobStep(jobRepository: JobRepository): Step =
StepBuilder(SnuttRatingSyncJobConfig.RATING_SYNC_JOB_NAME, jobRepository)
.job(ratingSyncJob)
.build()
}

data class SyncProcessResult(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.wafflestudio.snuttev.sync

import com.wafflestudio.snuttev.core.domain.lecture.model.SnuttLectureIdMap
import com.wafflestudio.snuttev.core.domain.lecture.repository.LectureRepository
import jakarta.persistence.EntityManagerFactory
import org.springframework.batch.core.Job
import org.springframework.batch.core.Step
import org.springframework.batch.core.job.builder.JobBuilder
import org.springframework.batch.core.repository.JobRepository
import org.springframework.batch.core.step.builder.StepBuilder
import org.springframework.batch.item.ItemWriter
import org.springframework.batch.item.database.JpaPagingItemReader
import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.Profile
import org.springframework.data.mongodb.core.BulkOperations
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query
import org.springframework.data.mongodb.core.query.Update
import org.springframework.orm.jpa.JpaTransactionManager

@Configuration
@Profile(value = ["!test"])
class SnuttRatingSyncJobConfig(
private val entityManagerFactory: EntityManagerFactory,
private val mongoTemplate: MongoTemplate,
private val lectureRepository: LectureRepository,
) {
companion object {
const val RATING_SYNC_JOB_NAME = "RATING_SYNC_JOB"
private const val CUSTOM_READER_JOB_STEP = RATING_SYNC_JOB_NAME + "_STEP"
private const val CHUNK_SIZE = 1000000
}

@Bean
fun ratingSyncJob(jobRepository: JobRepository): Job {
return JobBuilder(RATING_SYNC_JOB_NAME, jobRepository)
.start(customReaderStep(jobRepository))
.build()
}

private fun customReaderStep(jobRepository: JobRepository): Step {
return StepBuilder(CUSTOM_READER_JOB_STEP, jobRepository)
.chunk<SnuttLectureIdMap, SnuttLectureIdMap>(
CHUNK_SIZE,
JpaTransactionManager().apply {
this.entityManagerFactory = this@SnuttRatingSyncJobConfig.entityManagerFactory
},
)
.reader(reader())
.writer(writer())
.build()
}

private fun reader(): JpaPagingItemReader<SnuttLectureIdMap> =
JpaPagingItemReaderBuilder<SnuttLectureIdMap>()
.name("snuttLectureIdMapReader")
.entityManagerFactory(entityManagerFactory)
.queryString("SELECT s FROM SnuttLectureIdMap s JOIN FETCH s.semesterLecture")
.pageSize(CHUNK_SIZE)
.build()

private fun writer(): ItemWriter<SnuttLectureIdMap> {
return ItemWriter { items ->
val lectureIdtoLectureRatingMap =
lectureRepository.findAllRatingsByLectureIds(
items.mapNotNull { it.semesterLecture.lecture.id },
)
.associateBy { it.id }
val bulkOps = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, "lectures")
items.forEach {
val evInfo = lectureIdtoLectureRatingMap[it.semesterLecture.lecture.id]
bulkOps.updateOne(
Query(Criteria.where("_id").`is`(it.snuttId)),
Update().set("evInfo.evId", evInfo?.id)
.set("evInfo.avgRating", evInfo?.avgRating)
.set("evInfo.count", evInfo?.count),
)
}
bulkOps.execute()
}
}
}
1 change: 1 addition & 0 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies {
implementation("software.amazon.awssdk:secretsmanager:2.20.66")
implementation("software.amazon.awssdk:sts:2.20.66")

implementation("org.springframework.boot:spring-boot-starter-data-mongodb-reactive")
implementation("org.springframework.boot:spring-boot-starter-data-redis")

runtimeOnly("com.mysql:mysql-connector-j")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ import com.wafflestudio.snuttev.core.domain.evaluation.repository.LectureEvaluat
import com.wafflestudio.snuttev.core.domain.lecture.model.SemesterLecture
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.mongo.MongoService
import com.wafflestudio.snuttev.core.domain.tag.repository.TagRepository
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.data.repository.findByIdOrNull
Expand All @@ -51,6 +53,8 @@ class EvaluationService internal constructor(
private val evaluationReportRepository: EvaluationReportRepository,
private val evaluationLikeRepository: EvaluationLikeRepository,
private val cache: Cache,
private val snuttLectureIdMapRepository: SnuttLectureIdMapRepository,
private val mongoService: MongoService,
) {
companion object {
private const val DEFAULT_PAGE_SIZE = 20
Expand All @@ -77,6 +81,8 @@ class EvaluationService internal constructor(

cache.deleteAll(CacheKey.EVALUATIONS_BY_TAG_PAGE)

updateEvInfosBySemesterLecture(semesterLecture)

return genLectureEvaluationDto(lectureEvaluation)
}

Expand Down Expand Up @@ -276,6 +282,7 @@ class EvaluationService internal constructor(
}

cache.deleteAll(CacheKey.EVALUATIONS_BY_TAG_PAGE)
updateEvInfosBySemesterLecture(evaluation.semesterLecture)

val isLiked = evaluationLikeRepository.existsByLectureEvaluationAndUserId(evaluation, userId)
return EvaluationWithSemesterResponse.of(evaluation, userId, isLiked)
Expand Down Expand Up @@ -308,6 +315,7 @@ class EvaluationService internal constructor(
lectureEvaluation.isHidden = true

cache.deleteAll(CacheKey.EVALUATIONS_BY_TAG_PAGE)
updateEvInfosBySemesterLecture(lectureEvaluation.semesterLecture)
}

fun reportEvaluation(
Expand Down Expand Up @@ -391,4 +399,10 @@ class EvaluationService internal constructor(
content = evaluationReport.content,
isHidden = evaluationReport.isHidden,
)

private fun updateEvInfosBySemesterLecture(semesterLecture: SemesterLecture) {
val evInfo = lectureRepository.findAllRatingsByLectureIds(listOf(semesterLecture.lecture.id!!)).firstOrNull()
val snuttIds = snuttLectureIdMapRepository.findAllBySemesterLecture(semesterLecture).map { it.snuttId }
mongoService.updateEvInfoToSnuttIds(snuttIds, evInfo)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,13 @@ interface LectureRepository : JpaRepository<Lecture, Long?>, LectureRepositoryCu
""",
)
fun findAllRatingsByLectureIds(ids: Iterable<Long>): List<LectureRatingDao>

@Query(
"""
select new com.wafflestudio.snuttev.core.domain.lecture.model.LectureRatingDao(
sl.lecture.id, avg(le.rating), count(le.id)
) from LectureEvaluation le right join le.semesterLecture sl where le.isHidden = false group by sl.lecture.id
""",
)
fun findAllRatings(): List<LectureRatingDao>
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.wafflestudio.snuttev.core.domain.lecture.repository

import com.wafflestudio.snuttev.core.domain.lecture.model.SemesterLecture
import com.wafflestudio.snuttev.core.domain.lecture.model.SnuttLectureIdMap
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
Expand All @@ -8,4 +9,5 @@ interface SnuttLectureIdMapRepository : JpaRepository<SnuttLectureIdMap, Long> {
@Query("SELECT ttm FROM SnuttLectureIdMap ttm JOIN FETCH ttm.semesterLecture WHERE ttm.snuttId IN :snuttIds")
fun findAllWithSemesterLectureBySnuttIdIn(snuttIds: List<String>): List<SnuttLectureIdMap>
fun findBySnuttId(snuttId: String): SnuttLectureIdMap?
fun findAllBySemesterLecture(semesterLecture: SemesterLecture): List<SnuttLectureIdMap>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.wafflestudio.snuttev.core.domain.mongo

import com.wafflestudio.snuttev.core.domain.lecture.model.LectureRatingDao
import org.springframework.data.mongodb.core.ReactiveMongoTemplate
import org.springframework.data.mongodb.core.query.Criteria
import org.springframework.data.mongodb.core.query.Query
import org.springframework.data.mongodb.core.query.Update
import org.springframework.stereotype.Service

@Service
class MongoService(
private val mongoTemplate: ReactiveMongoTemplate,
) {
fun updateEvInfoToSnuttIds(snuttIds: List<String>, evInfo: LectureRatingDao?) =
mongoTemplate.updateMulti(
Query(Criteria.where("_id").`in`(snuttIds)),
Update().set("evInfo.evId", evInfo?.id)
.set("evInfo.avgRating", evInfo?.avgRating)
.set("evInfo.count", evInfo?.count),
"lectures",
).subscribe()
}

0 comments on commit cc9c988

Please sign in to comment.