-
Notifications
You must be signed in to change notification settings - Fork 1
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
[Feat/#221] 아티클 조회수 저장 방식 및 조회 방식 개선 #223
Changes from 8 commits
1c16093
d8acd58
6fca14c
5e41a3d
13306e6
3f6a688
b5f58ce
a1eb90a
dc4a03a
0ff043e
bc4d0ac
680cf75
c519e60
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 |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package com.few.api.repo.dao.article | ||
|
||
import com.few.api.repo.dao.article.command.ArticleViewCountCommand | ||
import com.few.api.repo.dao.article.query.ArticleViewCountQuery | ||
import jooq.jooq_dsl.Tables.ARTICLE_VIEW_COUNT | ||
import org.jooq.DSLContext | ||
import org.springframework.stereotype.Repository | ||
|
||
@Repository | ||
class ArticleViewCountDao( | ||
private val dslContext: DSLContext, | ||
) { | ||
|
||
fun upsertArticleViewCount(query: ArticleViewCountQuery) { | ||
dslContext.insertInto(ARTICLE_VIEW_COUNT) | ||
.set(ARTICLE_VIEW_COUNT.ARTICLE_ID, query.articleId) | ||
.set(ARTICLE_VIEW_COUNT.VIEW_COUNT, 1) | ||
.set(ARTICLE_VIEW_COUNT.CATEGORY_CD, query.categoryType?.code) | ||
.onDuplicateKeyUpdate() | ||
.set(ARTICLE_VIEW_COUNT.VIEW_COUNT, ARTICLE_VIEW_COUNT.VIEW_COUNT.plus(1)) | ||
} | ||
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. upsert 쿼리가 무엇인지 알 수 있나요? 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. 테이블에 업데이트할 로우가 없으면 인서트하고, 있으면 업데이트 |
||
|
||
fun selectArticleViewCount(command: ArticleViewCountCommand): Long? { | ||
return dslContext.select( | ||
ARTICLE_VIEW_COUNT.VIEW_COUNT | ||
).from(ARTICLE_VIEW_COUNT) | ||
.where(ARTICLE_VIEW_COUNT.ARTICLE_ID.eq(command.articleId)) | ||
.and(ARTICLE_VIEW_COUNT.DELETED_AT.isNull) | ||
.fetchOneInto(Long::class.java) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package com.few.api.repo.dao.article.command | ||
|
||
data class ArticleViewCountCommand( | ||
val articleId: Long, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.few.api.repo.dao.article.query | ||
|
||
import com.few.data.common.code.CategoryType | ||
|
||
data class ArticleViewCountQuery( | ||
val articleId: Long, | ||
val categoryType: CategoryType?, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.few.api.domain.article.handler | ||
|
||
import com.few.api.repo.dao.article.ArticleViewCountDao | ||
import com.few.api.repo.dao.article.command.ArticleViewCountCommand | ||
import org.springframework.stereotype.Component | ||
import org.springframework.transaction.annotation.Isolation | ||
import org.springframework.transaction.annotation.Propagation | ||
import org.springframework.transaction.annotation.Transactional | ||
|
||
@Component | ||
class ArticleViewCountHandler( | ||
private val articleViewCountDao: ArticleViewCountDao, | ||
) { | ||
@Transactional(isolation = Isolation.READ_UNCOMMITTED, propagation = Propagation.REQUIRES_NEW) | ||
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. 요거 @ViewTransactional 요런 어노테이션으로 선언 해버릴까요? |
||
fun browseArticleViewCount(articleId: Long): Long { | ||
return (articleViewCountDao.selectArticleViewCount(ArticleViewCountCommand(articleId)) ?: 0L) + 1L | ||
} | ||
} | ||
Comment on lines
+10
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. 아티클 조회수를 조회하는 서비스
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,11 @@ | ||
package com.few.api.domain.article.handler | ||
|
||
import com.few.api.config.DatabaseAccessThreadPoolConfig.Companion.DATABASE_ACCESS_POOL | ||
import com.few.api.repo.dao.article.ArticleViewCountDao | ||
import com.few.api.repo.dao.article.ArticleViewHisDao | ||
import com.few.api.repo.dao.article.command.ArticleViewHisCommand | ||
import com.few.api.repo.dao.article.query.ArticleViewCountQuery | ||
import com.few.data.common.code.CategoryType | ||
import io.github.oshai.kotlinlogging.KotlinLogging | ||
import org.springframework.scheduling.annotation.Async | ||
import org.springframework.stereotype.Component | ||
|
@@ -11,17 +14,24 @@ import org.springframework.transaction.annotation.Transactional | |
@Component | ||
class ArticleViewHisAsyncHandler( | ||
private val articleViewHisDao: ArticleViewHisDao, | ||
private val articleViewCountDao: ArticleViewCountDao, | ||
) { | ||
private val log = KotlinLogging.logger {} | ||
|
||
@Async(value = DATABASE_ACCESS_POOL) | ||
@Transactional | ||
fun addArticleViewHis(articleId: Long, memberId: Long) { | ||
fun addArticleViewHis(articleId: Long, memberId: Long, categoryType: CategoryType?) { | ||
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. UC에서 카테고리 가져올때 CategoryType 자체적으로 코드 -> 객체 변환시 nullable 리턴이라 저길 어떻게 바꾸면 좋을지… 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. 요청 전에 ?: 을 사용해서 확인하고 널이면 예외를 발생시키면 될 것같아요 |
||
try { | ||
articleViewHisDao.insertArticleViewHis(ArticleViewHisCommand(articleId, memberId)) | ||
log.debug { "Successfully inserted article view history for articleId: $articleId and memberId: $memberId" } | ||
|
||
articleViewCountDao.upsertArticleViewCount(ArticleViewCountQuery(articleId, categoryType)) | ||
log.debug { "Successfully upserted article view count for articleId: $articleId and memberId: $memberId" } | ||
} catch (e: Exception) { | ||
log.error { "Failed to insert article view history for articleId: $articleId and memberId: $memberId" } | ||
log.error { | ||
"Failed insertion article view history and upsertion article view count " + | ||
"for articleId: $articleId and memberId: $memberId" | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package com.few.api.domain.article.usecase | ||
|
||
import com.few.api.domain.article.handler.ArticleViewCountHandler | ||
import com.few.api.domain.article.handler.ArticleViewHisAsyncHandler | ||
import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseIn | ||
import com.few.api.domain.article.usecase.dto.ReadArticleUseCaseOut | ||
|
@@ -10,8 +11,6 @@ import com.few.api.domain.article.service.dto.BrowseArticleProblemIdsInDto | |
import com.few.api.domain.article.service.dto.ReadWriterRecordInDto | ||
import com.few.api.exception.common.NotFoundException | ||
import com.few.api.repo.dao.article.ArticleDao | ||
import com.few.api.repo.dao.article.ArticleViewHisDao | ||
import com.few.api.repo.dao.article.query.ArticleViewHisCountQuery | ||
import com.few.api.repo.dao.article.query.SelectArticleRecordQuery | ||
import com.few.data.common.code.CategoryType | ||
import org.springframework.stereotype.Component | ||
|
@@ -22,8 +21,8 @@ class ReadArticleUseCase( | |
private val articleDao: ArticleDao, | ||
private val readArticleWriterRecordService: ReadArticleWriterRecordService, | ||
private val browseArticleProblemsService: BrowseArticleProblemsService, | ||
private val articleViewHisDao: ArticleViewHisDao, | ||
private val articleViewHisAsyncHandler: ArticleViewHisAsyncHandler, | ||
private val articleViewCountHandler: ArticleViewCountHandler, | ||
) { | ||
|
||
@Transactional(readOnly = true) | ||
|
@@ -41,9 +40,13 @@ class ReadArticleUseCase( | |
browseArticleProblemsService.execute(query) | ||
} | ||
|
||
val views = (articleViewHisDao.countArticleViews(ArticleViewHisCountQuery(useCaseIn.articleId)) ?: 0L) + 1L | ||
|
||
articleViewHisAsyncHandler.addArticleViewHis(useCaseIn.articleId, useCaseIn.memberId) | ||
// ARTICLE VIEW HIS에 저장하기 전에 먼저 VIEW COUNT 조회하는 순서 변경 금지 | ||
val views = articleViewCountHandler.browseArticleViewCount(useCaseIn.articleId) | ||
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. 요친구는 왜 네이밍이 handler이죠? 저는 반환을 하니 service가 어울릴꺼 같아요 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. service도 생각했었는데 같은 도메인이여서 네이밍 뭘로할지 고민되네요 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. 진짜 애매하다.. |
||
articleViewHisAsyncHandler.addArticleViewHis( | ||
useCaseIn.articleId, | ||
useCaseIn.memberId, | ||
CategoryType.fromCode(articleRecord.category) | ||
) | ||
|
||
return ReadArticleUseCaseOut( | ||
id = articleRecord.articleId, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
-- article 별 조회수 저장 테이블 | ||
CREATE TABLE ARTICLE_VIEW_COUNT | ||
( | ||
article_id BIGINT NOT NULL, | ||
view_count BIGINT NOT NULL, | ||
category_cd TINYINT NOT NULL, | ||
deleted_at TIMESTAMP NULL DEFAULT NULL, | ||
CONSTRAINT article_view_count_pk PRIMARY KEY (article_id) | ||
); | ||
Comment on lines
+2
to
+9
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.
|
||
|
||
-- 조회수 순으로 아티클 조회시 사용하기 위한 인덱스 | ||
-- ex. SELECT * FROM ARTICLE_VIEW_COUNT ORDER BY view_count DESC LIMIT 10; | ||
CREATE INDEX article_view_count_idx1 ON ARTICLE_VIEW_COUNT (view_count DESC); | ||
|
||
-- 카테고리 별 필터링을 위한 인덱스 | ||
CREATE INDEX article_view_count_idx2 ON ARTICLE_VIEW_COUNT (category_cd); | ||
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. 특별히 카테고리를 넣은 이유는 메인 페이지에서 카테고리별 정렬될때 사용됨... article_view_count 테이블 자체가 거기서 메인페이지에서 아티클 ordering할 기준 테이블이 되기 때문에 여기에 카테고리가 있어야지 없으면 ordering할 떄 다시 아티클 테이블 가서 조회해 와야 함 |
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.
요거는 뭔가요?
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.
article_view_count 테이블 로우는 아티클 당 한개로 구성되는데, 첫번째로 조회될 땐 insert가 가고, 이미 row가 있는 경우(이미 1번 이상 조회됨) Update(view_count + 1) 쿼리가 날라가는 upsert 쿼리입니다
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.
아하 신기하다..!!!
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.
SQL 변환