Skip to content

Commit

Permalink
Merge pull request #87 from MARU-EGG/refactor/유사도-효율-증가
Browse files Browse the repository at this point in the history
[TSK-45] refactor: 유사도 효율 증가를 위한 구조 개선
  • Loading branch information
Hoya324 authored Sep 28, 2024
2 parents d18a1f2 + 9521cd3 commit be4ef7e
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
import mju.iphak.maru_egg.answer.dto.response.AnswerResponse;
import mju.iphak.maru_egg.common.utils.NLP.TextSimilarityUtils;
import mju.iphak.maru_egg.common.utils.PhraseExtractionUtils;
import mju.iphak.maru_egg.question.dao.request.SelectQuestionCores;
import mju.iphak.maru_egg.question.dao.response.QuestionCore;
import mju.iphak.maru_egg.question.domain.Question;
import mju.iphak.maru_egg.question.domain.QuestionCategory;
import mju.iphak.maru_egg.question.domain.QuestionType;
import mju.iphak.maru_egg.question.dto.response.QuestionCore;
import mju.iphak.maru_egg.question.dto.response.QuestionResponse;
import mju.iphak.maru_egg.question.repository.QuestionRepository;

Expand All @@ -39,7 +40,8 @@ public class QuestionProcessingService {
public QuestionResponse question(final QuestionType type, final QuestionCategory category, final String content) {
String contentToken = PhraseExtractionUtils.extractPhrases(content);

List<QuestionCore> questionCores = getQuestionCores(type, category, contentToken);
SelectQuestionCores selectQuestionCores = SelectQuestionCores.of(type, category, content, contentToken);
List<QuestionCore> questionCores = getQuestionCores(selectQuestionCores);
if (questionCores.isEmpty()) {
log.info("저장된 질문이 없어 새롭게 LLM서버에 질문을 요청합니다.");
return answerManager.processNewQuestion(type, category, content, contentToken);
Expand All @@ -59,12 +61,9 @@ public QuestionResponse getQuestion(final Long questionId) {
return getExistingQuestionResponse(questionId);
}

private List<QuestionCore> getQuestionCores(QuestionType type, QuestionCategory category, String contentToken) {
return category == null ?
questionRepository.searchQuestionsByContentTokenAndType(contentToken, type)
.orElse(Collections.emptyList()) :
questionRepository.searchQuestionsByContentTokenAndTypeAndCategory(contentToken, type, category)
.orElse(Collections.emptyList());
private List<QuestionCore> getQuestionCores(SelectQuestionCores selectQuestionCores) {
return questionRepository.searchQuestions(selectQuestionCores)
.orElse(Collections.emptyList());
}

private QuestionResponse getExistingQuestionResponse(Long questionId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import mju.iphak.maru_egg.answer.domain.Answer;
import mju.iphak.maru_egg.answer.dto.response.AnswerResponse;
import mju.iphak.maru_egg.common.dto.pagination.SliceQuestionResponse;
import mju.iphak.maru_egg.question.dao.request.SelectQuestions;
import mju.iphak.maru_egg.question.domain.Question;
import mju.iphak.maru_egg.question.domain.QuestionCategory;
import mju.iphak.maru_egg.question.domain.QuestionType;
Expand Down Expand Up @@ -47,18 +48,10 @@ public SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorP
final Integer size) {
Pageable pageable = PageRequest.of(0, size);
SliceQuestionResponse<SearchedQuestionsResponse> response;
response = questionRepository.searchQuestionsOfCursorPagingByContentWithLikeFunction(
type,
category,
content,
cursorViewCount, questionId, pageable);
if (response.data().isEmpty()) {
response = questionRepository.searchQuestionsOfCursorPagingByContentWithFullTextSearch(
type,
category,
content,
cursorViewCount, questionId, pageable);
}

SelectQuestions selectQuestions = SelectQuestions.of(type, category, content, cursorViewCount, questionId,
pageable);
response = questionRepository.searchQuestionsOfCursorPaging(selectQuestions);
return response;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package mju.iphak.maru_egg.question.dao.request;

import lombok.Builder;
import mju.iphak.maru_egg.question.domain.QuestionCategory;
import mju.iphak.maru_egg.question.domain.QuestionType;

@Builder
public record SelectQuestionCores(
QuestionType type,
QuestionCategory category,
String content,
String contentToken
) {

public static SelectQuestionCores of(final QuestionType type, final QuestionCategory category, final String content,
final String contentToken) {
return SelectQuestionCores.builder()
.type(type)
.category(category)
.content(content)
.contentToken(contentToken)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package mju.iphak.maru_egg.question.dao.request;

import org.springframework.data.domain.Pageable;

import lombok.Builder;
import mju.iphak.maru_egg.question.domain.QuestionCategory;
import mju.iphak.maru_egg.question.domain.QuestionType;

@Builder
public record SelectQuestions(
QuestionType type,
QuestionCategory category,
String content,
Integer cursorViewCount,
Long questionId,
Pageable pageable
) {
public static SelectQuestions of(QuestionType type,
QuestionCategory category,
String content,
Integer cursorViewCount,
Long questionId,
Pageable pageable) {
return SelectQuestions.builder()
.type(type)
.category(category)
.content(content)
.cursorViewCount(cursorViewCount)
.questionId(questionId)
.pageable(pageable)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package mju.iphak.maru_egg.question.dto.response;
package mju.iphak.maru_egg.question.dao.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,15 @@
import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Pageable;

import mju.iphak.maru_egg.common.dto.pagination.SliceQuestionResponse;
import mju.iphak.maru_egg.question.domain.QuestionCategory;
import mju.iphak.maru_egg.question.domain.QuestionType;
import mju.iphak.maru_egg.question.dto.response.QuestionCore;
import mju.iphak.maru_egg.question.dao.request.SelectQuestionCores;
import mju.iphak.maru_egg.question.dao.request.SelectQuestions;
import mju.iphak.maru_egg.question.dao.response.QuestionCore;
import mju.iphak.maru_egg.question.dto.response.SearchedQuestionsResponse;

public interface QuestionRepositoryCustom {
Optional<List<QuestionCore>> searchQuestionsByContentTokenAndType(final String contentToken,
final QuestionType type);

Optional<List<QuestionCore>> searchQuestionsByContentTokenAndTypeAndCategory(final String contentToken,
final QuestionType type,
final QuestionCategory category);

SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorPagingByContentWithFullTextSearch(
final QuestionType type, final QuestionCategory category, final String content,
final Integer cursorViewCount, final Long questionId, final Pageable pageable);
Optional<List<QuestionCore>> searchQuestions(final SelectQuestionCores selectQuestionCores);

SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorPagingByContentWithLikeFunction(
final QuestionType type, final QuestionCategory category, final String content,
final Integer cursorViewCount, final Long questionId, final Pageable pageable);
SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorPaging(
final SelectQuestions selectQuestions);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static mju.iphak.maru_egg.question.domain.QQuestion.*;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
Expand All @@ -19,10 +20,12 @@
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import mju.iphak.maru_egg.common.dto.pagination.SliceQuestionResponse;
import mju.iphak.maru_egg.question.dao.request.SelectQuestionCores;
import mju.iphak.maru_egg.question.dao.request.SelectQuestions;
import mju.iphak.maru_egg.question.dao.response.QuestionCore;
import mju.iphak.maru_egg.question.domain.Question;
import mju.iphak.maru_egg.question.domain.QuestionCategory;
import mju.iphak.maru_egg.question.domain.QuestionType;
import mju.iphak.maru_egg.question.dto.response.QuestionCore;
import mju.iphak.maru_egg.question.dto.response.SearchedQuestionsResponse;

@Repository
Expand All @@ -33,61 +36,53 @@ public class QuestionRepositoryImpl implements QuestionRepositoryCustom {

private final JPAQueryFactory queryFactory;

@Override
public Optional<List<QuestionCore>> searchQuestionsByContentTokenAndType(final String contentToken,
final QuestionType type) {
NumberTemplate<Double> booleanTemplate = createBooleanTemplate(contentToken);
List<Tuple> tuples = queryFactory.select(question.id, question.contentToken)
.from(question)
.where(booleanTemplate.gt(0).and(question.questionType.eq(type)))
.fetch();

List<QuestionCore> result = tuples.stream()
.map(tuple -> QuestionCore.of(tuple.get(question.id), tuple.get(question.contentToken)))
.collect(Collectors.toList());

return Optional.of(result);
public Optional<List<QuestionCore>> searchQuestions(final SelectQuestionCores selectQuestionCores) {
return Optional.of(
searchQuestionsByContentTokenAndType(
selectQuestionCores.content(),
selectQuestionCores.contentToken(),
selectQuestionCores.type(),
selectQuestionCores.category()
).orElse(Collections.emptyList())
);
}

@Override
public Optional<List<QuestionCore>> searchQuestionsByContentTokenAndTypeAndCategory(final String contentToken,
final QuestionType type, final QuestionCategory category) {
NumberTemplate<Double> booleanTemplate = createBooleanTemplate(contentToken);
List<Tuple> tuples = queryFactory.select(question.id, question.contentToken)
.from(question)
.where(booleanTemplate.gt(0)
.and(question.questionType.eq(type))
.and(question.questionCategory.eq(category)))
.fetch();

List<QuestionCore> result = tuples.stream()
.map(tuple -> QuestionCore.of(tuple.get(question.id), tuple.get(question.contentToken)))
.collect(Collectors.toList());

return Optional.of(result);
public SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorPaging(
final SelectQuestions selectQuestions) {
SliceQuestionResponse<SearchedQuestionsResponse> response = searchQuestionsOfCursorPagingByContentWithLikeFunction(
selectQuestions.type(),
selectQuestions.category(),
selectQuestions.content(),
selectQuestions.pageable());
if (response.data().isEmpty()) {
response = searchQuestionsOfCursorPagingByContentWithFullTextSearch(
selectQuestions.type(),
selectQuestions.category(),
selectQuestions.content(),
selectQuestions.cursorViewCount(), selectQuestions.questionId(), selectQuestions.pageable());
}
return response;
}

@Override
public SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorPagingByContentWithFullTextSearch(
private SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorPagingByContentWithFullTextSearch(
final QuestionType type, final QuestionCategory category, final String content,
final Integer cursorViewCount, final Long questionId, final Pageable pageable) {

int pageSize = pageable.getPageSize();
Integer viewCountCursorKey = getValidCursorViewCount(cursorViewCount);
Long questionIdCursorKey = getValidCursorQuestionId(questionId);

NumberTemplate<Double> booleanTemplate = createBooleanTemplate(content);
NumberTemplate<Double> booleanTemplate = createBooleanTemplateByContentToken(content);
BooleanExpression cursorPredicate = createCursorPredicate(viewCountCursorKey, questionIdCursorKey);

List<Question> questions = fetchQuestions(type, category, booleanTemplate, cursorPredicate, pageSize);

return buildSliceQuestionResponse(pageable, pageSize, questions);
}

@Override
public SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorPagingByContentWithLikeFunction(
final QuestionType type, final QuestionCategory category, final String content,
final Integer cursorViewCount, final Long questionId, final Pageable pageable) {
private SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorPagingByContentWithLikeFunction(
final QuestionType type, final QuestionCategory category, final String content, final Pageable pageable) {

int pageSize = pageable.getPageSize();

Expand All @@ -100,7 +95,47 @@ public SliceQuestionResponse<SearchedQuestionsResponse> searchQuestionsOfCursorP
return buildSliceQuestionResponse(pageable, pageSize, questions);
}

private NumberTemplate<Double> createBooleanTemplate(String content) {
private Optional<List<QuestionCore>> searchQuestionsByContentTokenAndType(
final String content, final String contentToken, final QuestionType type, final QuestionCategory category) {

NumberTemplate<Double> booleanTemplate = createBooleanTemplateByContent(content);

List<Tuple> tuples = fetchQuestionsByContentAndTypeAndCategory(booleanTemplate, type, category);

if (tuples.isEmpty()) {
booleanTemplate = createBooleanTemplateByContentToken(contentToken);
tuples = fetchQuestionsByContentAndTypeAndCategory(booleanTemplate, type, category);
}

List<QuestionCore> result = tuples.stream()
.map(tuple -> QuestionCore.of(tuple.get(question.id), tuple.get(question.contentToken)))
.collect(Collectors.toList());

return Optional.of(result);
}

private List<Tuple> fetchQuestionsByContentAndTypeAndCategory(
NumberTemplate<Double> booleanTemplate, QuestionType type, QuestionCategory category) {

BooleanBuilder whereClause = new BooleanBuilder();
whereClause.and(booleanTemplate.gt(0));
whereClause.and(question.questionType.eq(type));

if (category != null) {
whereClause.and(question.questionCategory.eq(category));
}

return queryFactory.select(question.id, question.contentToken)
.from(question)
.where(whereClause)
.fetch();
}

private NumberTemplate<Double> createBooleanTemplateByContentToken(String contentToken) {
return Expressions.numberTemplate(Double.class, "function('match', {0}, {1})", question.content, contentToken);
}

private NumberTemplate<Double> createBooleanTemplateByContent(String content) {
return Expressions.numberTemplate(Double.class, "function('match', {0}, {1})", question.content, content);
}

Expand Down Expand Up @@ -175,8 +210,7 @@ private SliceQuestionResponse<SearchedQuestionsResponse> buildSliceQuestionRespo
}

List<SearchedQuestionsResponse> questionResponses = questions.stream()
.map(q -> SearchedQuestionsResponse.
of(q.getId(), q.getContent(), q.isChecked()))
.map(q -> SearchedQuestionsResponse.of(q.getId(), q.getContent(), q.isChecked()))
.collect(Collectors.toList());

Integer nextCursorViewCount = null;
Expand Down
Loading

0 comments on commit be4ef7e

Please sign in to comment.