Skip to content

Commit

Permalink
Merge pull request #20 from KUSITMS-MOAMOA/refactor/#16
Browse files Browse the repository at this point in the history
[Refactor/#16] 불필요한 DB 접근 제거 및 서비스 Interface 생성
  • Loading branch information
daeun084 authored Feb 17, 2025
2 parents 0966719 + 35c29e2 commit 3899eb4
Show file tree
Hide file tree
Showing 30 changed files with 1,234 additions and 1,102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import corecord.dev.domain.ability.domain.entity.Keyword;
import corecord.dev.domain.ability.domain.repository.AbilityRepository;
import corecord.dev.domain.folder.domain.entity.Folder;
import corecord.dev.domain.user.domain.entity.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -37,12 +36,12 @@ public void deleteAbilityList(List<Ability> abilityList) {
abilityRepository.deleteAll(abilityList);
}

public List<AbilityResponse.KeywordStateDto> findKeywordGraph(User user) {
return abilityRepository.findKeywordStateDtoList(user);
public List<AbilityResponse.KeywordStateDto> findKeywordGraph(Long userId) {
return abilityRepository.findKeywordStateDtoList(userId);
}

public List<String> findKeywordList(User user) {
return abilityRepository.getKeywordList(user).stream()
public List<String> findKeywordList(Long userId) {
return abilityRepository.getKeywordList(userId).stream()
.map(Keyword::getValue)
.toList();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,110 +1,18 @@
package corecord.dev.domain.ability.application;

import corecord.dev.domain.ability.domain.converter.AbilityConverter;

import corecord.dev.domain.ability.domain.dto.response.AbilityResponse;
import corecord.dev.domain.ability.domain.entity.Ability;
import corecord.dev.domain.ability.domain.entity.Keyword;
import corecord.dev.domain.ability.status.AbilityErrorStatus;
import corecord.dev.domain.ability.exception.AbilityException;
import corecord.dev.domain.analysis.domain.entity.Analysis;
import corecord.dev.domain.user.application.UserDbService;
import corecord.dev.domain.user.domain.entity.User;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class AbilityService {
private final EntityManager entityManager;
private final UserDbService userDbService;
private final AbilityDbService abilityDbService;

/*
* user의 역량 키워드 리스트를 반환
* @param userId
* @return
*/
@Transactional(readOnly = true)
public AbilityResponse.KeywordListDto getKeywordList(Long userId) {
User user = userDbService.findUserById(userId);
List<String> keywordList = abilityDbService.findKeywordList(user);

return AbilityConverter.toKeywordListDto(keywordList);
}

/*
* user의 각 역량 키워드에 대한 개수, 퍼센티지 정보를 반환
* @param userId
* @return
*/
@Transactional(readOnly = true)
public AbilityResponse.GraphDto getKeywordGraph(Long userId) {
User user = userDbService.findUserById(userId);

// keyword graph 정보 조회
List<AbilityResponse.KeywordStateDto> keywordGraph = abilityDbService.findKeywordGraph(user);

return AbilityConverter.toGraphDto(keywordGraph);
}

// CLOVA STUDIO를 통해 얻은 키워드 정보 파싱
@Transactional
public void parseAndSaveAbilities(Map<String, String> keywordList, Analysis analysis, User user) {
int abilityCount = 0;
for (Map.Entry<String, String> entry : keywordList.entrySet()) {
Keyword keyword = Keyword.getName(entry.getKey());

if (keyword == null) continue;

Ability ability = AbilityConverter.toAbility(keyword, entry.getValue(), analysis, user);
abilityDbService.saveAbility(ability);

if (analysis.getAbilityList() != null)
analysis.addAbility(ability);
abilityCount++;
}

validAbilityCount(abilityCount);
}

private void validAbilityCount(int abilityCount) {
if (abilityCount < 1 || abilityCount > 3)
throw new AbilityException(AbilityErrorStatus.INVALID_ABILITY_KEYWORD);
}

@Transactional
public void deleteOriginAbilityList(Analysis analysis) {
List<Ability> abilityList = analysis.getAbilityList();

if (!abilityList.isEmpty()) {
// 연관된 abilities 삭제
abilityDbService.deleteAbilityList(abilityList);

// Analysis에서 abilities 리스트 비우기
analysis.getAbilityList().clear();
entityManager.flush();
}
}
public interface AbilityService {

@Transactional
public void updateAbilityContents(Analysis analysis, Map<String, String> abilityMap) {
abilityMap.forEach((keyword, content) -> {
Keyword key = Keyword.getName(keyword);
Ability ability = findAbilityByKeyword(analysis, key);
ability.updateContent(content);
});
}
AbilityResponse.KeywordListDto getKeywordList(Long userId);
AbilityResponse.GraphDto getKeywordGraph(Long userId);

private Ability findAbilityByKeyword(Analysis analysis, Keyword key) {
// keyword가 기존 역량 분석에 존재했는지 확인
return analysis.getAbilityList().stream()
.filter(ability -> ability.getKeyword().equals(key))
.findFirst()
.orElseThrow(() -> new AbilityException(AbilityErrorStatus.INVALID_KEYWORD));
}
void parseAndSaveAbilities(Map<String, String> keywordList, Analysis analysis, User user);
void deleteOriginAbilityList(Analysis analysis);
void updateAbilityContents(Analysis analysis, Map<String, String> abilityMap);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package corecord.dev.domain.ability.application;

import corecord.dev.domain.ability.domain.converter.AbilityConverter;
import corecord.dev.domain.ability.domain.dto.response.AbilityResponse;
import corecord.dev.domain.ability.domain.entity.Ability;
import corecord.dev.domain.ability.domain.entity.Keyword;
import corecord.dev.domain.ability.exception.AbilityException;
import corecord.dev.domain.ability.status.AbilityErrorStatus;
import corecord.dev.domain.analysis.domain.entity.Analysis;
import corecord.dev.domain.user.domain.entity.User;
import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class AbilityServiceImpl implements AbilityService {

private final EntityManager entityManager;
private final AbilityDbService abilityDbService;

/**
* user의 역량 키워드 리스트를 반환합니다.
* 정렬 기준: 개수 내림차순, 최근 생성 순
*
* @param userId
* @return String type 키워드 명칭 리스트
*/
@Override
@Transactional(readOnly = true)
public AbilityResponse.KeywordListDto getKeywordList(Long userId) {
List<String> keywordList = abilityDbService.findKeywordList(userId);
return AbilityConverter.toKeywordListDto(keywordList);
}

/**
* user의 각 역량 키워드에 대한 개수, 퍼센티지 정보를 반환합니다.
* 정렬 기준: 퍼센티지 내림차순
*
* @param userId
* @return 각 키워드의 명칭, 개수, 전체 키워드 중 퍼센트 정보를 담은 리스트
*/
@Override
@Transactional(readOnly = true)
public AbilityResponse.GraphDto getKeywordGraph(Long userId) {
List<AbilityResponse.KeywordStateDto> keywordGraph = abilityDbService.findKeywordGraph(userId);
return AbilityConverter.toGraphDto(keywordGraph);
}

/**
* AI를 통해 얻은 키워드 정보를 파싱해 저장합니다.
* keywordList를 순회하며 최대 3개의 Keyword를 파싱합니다.
* 파싱된 데이터를 기반으로 Ability entity를 생성 및 저장합니다.
*
* @param keywordList AI로부터 받은 {키워드: 키워드 코멘트} 데이터
* @param analysis
* @param user
*/
@Override
@Transactional
public void parseAndSaveAbilities(Map<String, String> keywordList, Analysis analysis, User user) {
int abilityCount = 0;
for (Map.Entry<String, String> entry : keywordList.entrySet()) {
Keyword keyword = Keyword.getName(entry.getKey());

if (keyword == null) continue;

Ability ability = AbilityConverter.toAbility(keyword, entry.getValue(), analysis, user);
abilityDbService.saveAbility(ability);

if (analysis.getAbilityList() != null)
analysis.addAbility(ability);
abilityCount++;
}
validAbilityCount(abilityCount);
}

private void validAbilityCount(int abilityCount) {
if (abilityCount < 1 || abilityCount > 3)
throw new AbilityException(AbilityErrorStatus.INVALID_ABILITY_KEYWORD);
}

/**
* Analysis와 연관된 모든 Ability entity를 제거합니다.
*
* @param analysis
*/
@Override
@Transactional
public void deleteOriginAbilityList(Analysis analysis) {
List<Ability> abilityList = analysis.getAbilityList();

if (!abilityList.isEmpty()) {
// 연관된 abilities 삭제
abilityDbService.deleteAbilityList(abilityList);

// Analysis에서 abilities 리스트 비우기
analysis.getAbilityList().clear();
entityManager.flush();
}
}

/**
* 기존 Analysis의 역량 분석 데이터를 변경합니다.
* 기존 keyword의 content를 새로운 응답값으로 변경합니다.
*
* @param analysis
* @param abilityMap 새로운 AI 역량 분석 응답값
*/
@Override
@Transactional
public void updateAbilityContents(Analysis analysis, Map<String, String> abilityMap) {
abilityMap.forEach((keyword, content) -> {
Keyword key = Keyword.getName(keyword);
Ability ability = findAbilityByKeyword(analysis, key);
ability.updateContent(content);
});
}

private Ability findAbilityByKeyword(Analysis analysis, Keyword key) {
// keyword가 기존 역량 분석에 존재했는지 확인
return analysis.getAbilityList().stream()
.filter(ability -> ability.getKeyword().equals(key))
.findFirst()
.orElseThrow(() -> new AbilityException(AbilityErrorStatus.INVALID_KEYWORD));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "ability",
indexes = {@Index(name = "user_keyword_created_idx", columnList = "user_id, keyword, created_at")})
indexes = {@Index(name = "user_keyword_created_idx", columnList = "user_id, keyword, created_at"),
@Index(name = "ability_analysis_idx", columnList = "analysis_id, user_id, keyword")})
public class Ability extends BaseEntity {

@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import corecord.dev.domain.ability.domain.entity.Ability;
import corecord.dev.domain.ability.domain.entity.Keyword;
import corecord.dev.domain.folder.domain.entity.Folder;
import corecord.dev.domain.user.domain.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -17,12 +16,12 @@
public interface AbilityRepository extends JpaRepository<Ability, Long> {
@Query("SELECT new corecord.dev.domain.ability.domain.dto.response.AbilityResponse$KeywordStateDto(" +
"a.keyword, COUNT(a), " + // 각 키워드의 개수 집계
"(COUNT(a) * 1.0 / (SELECT COUNT(a2) FROM Ability a2 WHERE a2.user = :user)) * 100.0) " + // 각 키워드의 비율 집계
"(COUNT(a) * 1.0 / (SELECT COUNT(a2) FROM Ability a2 WHERE a2.user.userId = :userId)) * 100.0) " + // 각 키워드의 비율 집계
"FROM Ability a " +
"WHERE a.user = :user " +
"WHERE a.user.userId = :userId " +
"GROUP BY a.keyword " +
"ORDER BY 3 desc ") // 비율 높은 순 정렬
List<AbilityResponse.KeywordStateDto> findKeywordStateDtoList(@Param(value = "user") User user);
"ORDER BY COUNT(a) DESC, MAX(a.createdAt) DESC ") // 개수 많은 순 정렬
List<AbilityResponse.KeywordStateDto> findKeywordStateDtoList(@Param(value = "userId") Long userId);

@Modifying
@Query("DELETE " +
Expand All @@ -39,8 +38,8 @@ public interface AbilityRepository extends JpaRepository<Ability, Long> {
@Query("SELECT distinct a.keyword AS keyword " + // unique한 keyword list 반환
"FROM Ability a " +
"JOIN a.analysis ana " +
"WHERE a.user = :user " +
"WHERE a.user.userId = :userId " +
"GROUP BY a.keyword " +
"ORDER BY COUNT(a.keyword) DESC, MAX(ana.createdAt) DESC ") // 개수가 많은 순, 최근 생성 순 정렬
List<Keyword> getKeywordList(@Param(value = "user") User user);
List<Keyword> getKeywordList(@Param(value = "userId") Long userId);
}
Loading

0 comments on commit 3899eb4

Please sign in to comment.