Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release #114

Merged
merged 3 commits into from
Sep 24, 2024
Merged
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
1 change: 0 additions & 1 deletion batch/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ dependencies {
implementation(project(":core"))

implementation("org.springframework.boot:spring-boot-starter-batch")
implementation("org.springframework.boot:spring-boot-starter-data-mongodb")
runtimeOnly("org.postgresql:postgresql")
runtimeOnly("com.h2database:h2")
}
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 = [email protected]
},
)
.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 build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ subprojects {
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

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

implementation("com.wafflestudio.truffle.sdk:truffle-spring-boot-starter:1.1.2")
implementation("com.wafflestudio.truffle.sdk:truffle-logback:1.1.2")
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,24 @@
package com.wafflestudio.snuttev.core.domain.mongo

import com.wafflestudio.snuttev.core.domain.lecture.model.LectureRatingDao
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.stereotype.Service

@Service
class MongoService(
private val mongoTemplate: MongoTemplate,
) {
fun updateEvInfoToSnuttIds(snuttIds: List<String>, evInfo: LectureRatingDao?) =
runCatching {
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",
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ import com.wafflestudio.snuttev.core.domain.lecture.model.Lecture
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.mongo.MongoService
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatNoException
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
import org.springframework.data.repository.findByIdOrNull
import org.springframework.transaction.annotation.Transactional
import kotlin.random.Random
Expand All @@ -34,6 +36,7 @@ class EvaluationServiceTest @Autowired constructor(
private val lectureRepository: LectureRepository,
private val semesterLectureRepository: SemesterLectureRepository,
private val evaluationLikeRepository: EvaluationLikeRepository,
@MockBean private val mongoService: MongoService,
) {
@BeforeEach
fun setup() {
Expand Down
Loading