-
Notifications
You must be signed in to change notification settings - Fork 2
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
Impl likeCount and isLiked Logic #79
Changes from 15 commits
f165a86
e28180b
5341910
4112e6d
10c3712
2a75323
c499deb
3d0ddf0
fe79c23
d1f3b94
aa35b70
61b443d
7c6cba7
51e0c94
6c9d311
71848e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,14 +2,20 @@ package com.wafflestudio.snuttev.core.domain.evaluation.model | |
|
||
import com.wafflestudio.snuttev.core.common.model.BaseEntity | ||
import com.wafflestudio.snuttev.core.domain.lecture.model.SemesterLecture | ||
import org.hibernate.annotations.DynamicUpdate | ||
import org.hibernate.annotations.OptimisticLockType | ||
import org.hibernate.annotations.OptimisticLocking | ||
import java.time.LocalDateTime | ||
import javax.persistence.Column | ||
import javax.persistence.Entity | ||
import javax.persistence.FetchType | ||
import javax.persistence.JoinColumn | ||
import javax.persistence.ManyToOne | ||
import javax.persistence.OneToMany | ||
|
||
@Entity | ||
@DynamicUpdate | ||
@OptimisticLocking(type = OptimisticLockType.DIRTY) | ||
Comment on lines
+17
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요것도 괜찮네요 |
||
class LectureEvaluation( | ||
@ManyToOne(optional = false, fetch = FetchType.LAZY) | ||
@JoinColumn(name = "semester_lecture_id", nullable = false) | ||
|
@@ -49,4 +55,7 @@ class LectureEvaluation( | |
val fromSnuev: Boolean = false, | ||
|
||
createdAt: LocalDateTime = LocalDateTime.now(), | ||
|
||
@OneToMany(mappedBy = "lectureEvaluation") | ||
val evaluationLikes: List<EvaluationLike> = listOf() | ||
) : BaseEntity(createdAt = createdAt) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package com.wafflestudio.snuttev.core.domain.evaluation.repository | ||
|
||
import com.querydsl.core.BooleanBuilder | ||
import com.querydsl.core.types.dsl.CaseBuilder | ||
import com.querydsl.jpa.JPAExpressions.select | ||
import com.querydsl.jpa.impl.JPAQueryFactory | ||
import com.wafflestudio.snuttev.core.common.error.WrongMainTagException | ||
|
@@ -10,6 +11,7 @@ import com.wafflestudio.snuttev.core.domain.evaluation.dto.EvaluationWithLecture | |
import com.wafflestudio.snuttev.core.domain.evaluation.dto.EvaluationWithSemesterDto | ||
import com.wafflestudio.snuttev.core.domain.evaluation.dto.QEvaluationWithLectureDto | ||
import com.wafflestudio.snuttev.core.domain.evaluation.dto.QEvaluationWithSemesterDto | ||
import com.wafflestudio.snuttev.core.domain.evaluation.model.QEvaluationLike.evaluationLike | ||
import com.wafflestudio.snuttev.core.domain.evaluation.model.QLectureEvaluation.lectureEvaluation | ||
import com.wafflestudio.snuttev.core.domain.lecture.model.QLecture.lecture | ||
import com.wafflestudio.snuttev.core.domain.lecture.model.QSemesterLecture.semesterLecture | ||
|
@@ -23,7 +25,7 @@ class LectureEvaluationRepositoryImpl(private val queryFactory: JPAQueryFactory) | |
cursor: EvaluationCursor?, | ||
pageSize: Int, | ||
): List<EvaluationWithSemesterDto> = queryFactory | ||
.selectEvaluationWithSemesterDto() | ||
.selectEvaluationWithSemesterDto(userId) | ||
.where(semesterLecture.lecture.id.eq(lectureId)) | ||
.where(lectureEvaluation.userId.ne(userId)) | ||
.where(lectureEvaluation.isHidden.isFalse) | ||
|
@@ -36,7 +38,7 @@ class LectureEvaluationRepositoryImpl(private val queryFactory: JPAQueryFactory) | |
lectureId: Long, | ||
userId: String, | ||
): List<EvaluationWithSemesterDto> = queryFactory | ||
.selectEvaluationWithSemesterDto() | ||
.selectEvaluationWithSemesterDto(userId) | ||
.where(semesterLecture.lecture.id.eq(lectureId)) | ||
.where(lectureEvaluation.userId.eq(userId)) | ||
.where(lectureEvaluation.isHidden.isFalse) | ||
|
@@ -48,7 +50,7 @@ class LectureEvaluationRepositoryImpl(private val queryFactory: JPAQueryFactory) | |
cursor: Long?, | ||
pageSize: Int, | ||
): List<EvaluationWithLectureDto> = queryFactory | ||
.selectEvaluationWithLectureDto() | ||
.selectEvaluationWithLectureDto(userId) | ||
.where(lectureEvaluation.userId.eq(userId)) | ||
.where(lectureEvaluation.isHidden.isFalse) | ||
.where(getEvaluationIdCursorPredicate(cursor)) | ||
|
@@ -57,12 +59,13 @@ class LectureEvaluationRepositoryImpl(private val queryFactory: JPAQueryFactory) | |
.fetch() | ||
|
||
override fun findEvaluationWithLectureByTagAndClassification( | ||
userId: String, | ||
tag: Tag, | ||
classification: LectureClassification, | ||
cursor: Long?, | ||
pageSize: Int | ||
pageSize: Int, | ||
): List<EvaluationWithLectureDto> = queryFactory | ||
.selectEvaluationWithLectureDto() | ||
.selectEvaluationWithLectureDto(userId) | ||
.where(getMainTagPredicate(tag)) | ||
.where(semesterLecture.classification.eq(classification)) | ||
.where(lectureEvaluation.isHidden.isFalse) | ||
|
@@ -101,7 +104,7 @@ class LectureEvaluationRepositoryImpl(private val queryFactory: JPAQueryFactory) | |
) | ||
} | ||
|
||
private fun JPAQueryFactory.selectEvaluationWithSemesterDto() = select( | ||
private fun JPAQueryFactory.selectEvaluationWithSemesterDto(userId: String) = select( | ||
QEvaluationWithSemesterDto( | ||
lectureEvaluation.id, | ||
lectureEvaluation.userId, | ||
|
@@ -114,6 +117,7 @@ class LectureEvaluationRepositoryImpl(private val queryFactory: JPAQueryFactory) | |
lectureEvaluation.likeCount, | ||
lectureEvaluation.isHidden, | ||
lectureEvaluation.isReported, | ||
CaseBuilder().`when`(evaluationLike.id.isNull).then(false).otherwise(true).`as`("isLiked"), | ||
lectureEvaluation.fromSnuev, | ||
semesterLecture.year, | ||
semesterLecture.semester, | ||
|
@@ -122,8 +126,9 @@ class LectureEvaluationRepositoryImpl(private val queryFactory: JPAQueryFactory) | |
) | ||
.from(lectureEvaluation) | ||
.innerJoin(lectureEvaluation.semesterLecture, semesterLecture) | ||
.leftJoin(lectureEvaluation.evaluationLikes, evaluationLike).on(evaluationLike.userId.eq(userId)) | ||
|
||
private fun JPAQueryFactory.selectEvaluationWithLectureDto() = select( | ||
private fun JPAQueryFactory.selectEvaluationWithLectureDto(userId: String) = select( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 쿼리가 많이 빡세네요.. 추후에 개선 포인트로 두면 좋을 것 같습니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ㅋㅋ 좋습니다. 쿼리가 빡세다고 판단하신 이유는 join 3개 붙어서인가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵. 근데 무엇보다 where 절에서 tag 구하려고 avg 들어가는게 있는데 풀스캔이 돌 것 같아서 요것만 잘 분리해도 좋을 것 같아용. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. select le.id as col_0_0_,
le.user_id as col_1_0_,
le.content as col_2_0_,
le.grade_satisfaction as col_3_0_,
le.teaching_skill as col_4_0_,
le.gains as col_5_0_,
le.life_balance as col_6_0_,
le.rating as col_7_0_,
le.like_count as col_8_0_,
le.is_hidden as col_9_0_,
le.is_reported as col_10_0_,
IF(elike.id is null, false, true) as col_11_0_,
le.from_snuev as col_12_0_,
sl.year as col_13_0_,
sl.semester as col_14_0_,
sl.lecture_id as col_15_0_,
lecture.title as col_16_0_,
lecture.instructor as col_17_0_
from lecture_evaluation le
inner join semester_lecture sl on le.semester_lecture_id = sl.id
left outer join evaluation_like elike
on le.id = elike.lecture_evaluation_id and elike.user_id = '61f55ab5b7a1c400103aed78'
inner join lecture on sl.lecture_id = lecture.id
where (
sl.lecture_id in (select sl_tmp.lecture_id
from lecture_evaluation le_tmp
inner join
semester_lecture sl_tmp on le_tmp.semester_lecture_id = sl_tmp.id
where le_tmp.is_hidden = false
group by sl_tmp.lecture_id
having avg(le_tmp.rating) >= 4.0)
)
and sl.classification = '교양'
and le.is_hidden = false
order by le.id desc
limit 20; 참고로 현재 쿼리... ㅋㅋㅋ where subquery 로 실행되는 부분을 따로 query 해서 page size (20) 개수만 가져온 다음,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @PFCJeong 질문이 많아 죄송해요! 요거 시간 나실 때 답해주시면 추후 PR 에 참고하겠습니다. (요것도 #79 (comment)?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 칼럼을 추가해서 강의평이 추가, 삭제, 수정될 때마다 미리 통계를 내서 태그 달아두면 좋을 것 같았어요.. 대대적인 공사가 필요하겠지만.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아하 ㅋㅋㅋ 비정규화해서 저장해두는 거네요. 그렇게 하면 정말 대공사가 필요하긴 하겠군요. |
||
QEvaluationWithLectureDto( | ||
lectureEvaluation.id, | ||
lectureEvaluation.userId, | ||
|
@@ -136,6 +141,7 @@ class LectureEvaluationRepositoryImpl(private val queryFactory: JPAQueryFactory) | |
lectureEvaluation.likeCount, | ||
lectureEvaluation.isHidden, | ||
lectureEvaluation.isReported, | ||
CaseBuilder().`when`(evaluationLike.id.isNull).then(false).otherwise(true).`as`("isLiked"), | ||
lectureEvaluation.fromSnuev, | ||
semesterLecture.year, | ||
semesterLecture.semester, | ||
|
@@ -146,6 +152,7 @@ class LectureEvaluationRepositoryImpl(private val queryFactory: JPAQueryFactory) | |
) | ||
.from(lectureEvaluation) | ||
.innerJoin(lectureEvaluation.semesterLecture, semesterLecture) | ||
.leftJoin(lectureEvaluation.evaluationLikes, evaluationLike).on(evaluationLike.userId.eq(userId)) | ||
.innerJoin(semesterLecture.lecture, lecture) | ||
|
||
private fun getEvaluationCursorPredicate(cursor: EvaluationCursor?) = BooleanBuilder( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ package com.wafflestudio.snuttev.core.domain.evaluation.service | |
import com.wafflestudio.snuttev.core.common.dto.common.CursorPaginationResponse | ||
import com.wafflestudio.snuttev.core.common.error.EvaluationAlreadyExistsException | ||
import com.wafflestudio.snuttev.core.common.error.EvaluationLikeAlreadyExistsException | ||
import com.wafflestudio.snuttev.core.common.error.EvaluationLikeAlreadyNotExistsException | ||
import com.wafflestudio.snuttev.core.common.error.EvaluationReportAlreadyExistsException | ||
import com.wafflestudio.snuttev.core.common.error.LectureEvaluationNotFoundException | ||
import com.wafflestudio.snuttev.core.common.error.LectureNotFoundException | ||
|
@@ -186,12 +187,23 @@ class EvaluationService internal constructor( | |
val classification = LectureClassification.LIBERAL_EDUCATION | ||
|
||
var evaluationWithLectureDtos = cache.withCache( | ||
CacheKey.EVALUATIONS_BY_TAG_CLASSIFICATION_PAGE.build( | ||
builtCacheKey = CacheKey.EVALUATIONS_BY_TAG_CLASSIFICATION_PAGE.build( | ||
tagId, classification, evaluationIdCursor, DEFAULT_PAGE_SIZE + 1, | ||
), | ||
postHitProcessor = { dtos -> | ||
val evaluationsIds = dtos.map { it.id } | ||
val likes = evaluationLikeRepository.findAllByLectureEvaluationIdIn(evaluationsIds) | ||
dtos.map { dto -> | ||
dto.copy( | ||
likeCount = likes.count { it.lectureEvaluation.id == dto.id }.toLong(), | ||
isLiked = likes.any { it.lectureEvaluation.id == dto.id && it.userId == userId }, | ||
) | ||
} | ||
}, | ||
Comment on lines
+193
to
+202
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cache hit 해놓고도 DB 에 접근하는 게 슬프지만, 일단 너무 많은 걸 당장 바꾸지 않고 기능이 완벽하게 동작하는 것에 집중했습니다. single thread 인 Redis 없이 DB 로만 해결하는 방법도 일부러 해보고 싶었고요. 더 좋은 방식에 대한 의견은 남겨놔주세요. ex> like_count 를 Redis 로 관리하자 등. (그런다면 is_liked 도 그렇게 해야할까?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. like 관련 로직은 새로 서비스를 하나 파서 분리하면 좋을 것 같습니다. 제 개인적인 의견으로는 둘 다 인메모리 캐싱을 해도 될 거 같아요. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 캐시 키는 user id 와 evaluation id 로 생각하시나요? 유저가 공감/공감 취소할 때마다 캐시 날리구요? Redis 가 아닌 인 메모리로 보시는 이유도 궁금합니다. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 인메모리가 더 빠르고, api 스펙 자체가 리소스 부담을 줄이기 위해 캐시 size를 작게 잡아도 miss가 많이 생길것 같지 않아서요. |
||
) { | ||
val tag = tagRepository.findByIdOrNull(tagId) ?: throw TagNotFoundException | ||
lectureEvaluationRepository.findEvaluationWithLectureByTagAndClassification( | ||
userId, | ||
tag, | ||
classification, | ||
evaluationIdCursor, | ||
|
@@ -282,7 +294,7 @@ class EvaluationService internal constructor( | |
val evaluation = lectureEvaluationRepository.findByIdAndIsHiddenFalse(lectureEvaluationId) ?: throw LectureEvaluationNotFoundException | ||
|
||
val deletedRowCount = evaluationLikeRepository.deleteByLectureEvaluationAndUserId(evaluation, userId) | ||
if (deletedRowCount == 0L) return | ||
if (deletedRowCount == 0L) throw EvaluationLikeAlreadyNotExistsException | ||
|
||
evaluation.likeCount-- | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FasterXML/jackson-module-kotlin#80 이런 이슈 때문에 넣었던 건데 예전에 업데이트 하면서 필요 없어짐.