1. 온보딩 API 수정
기획 단계에서 온보딩 시 닉네임, 이메일, 한 줄 소개 등을 받기로 변경되어 이를 반영했습니다.
테스트 과정에서 온보딩 완료 후 유저 프로필 생성 시 유저 데이터 수집 메서드에서
Post 엔티티 -> Keyword 엔티티 접근 과정 중 N+1 문제가 발생하여,
페이징이 있는 조회이므로 @BatchSize 을 통해 해결하였습니다.
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
@BatchSize(size = 100)
private List<PostKeyword> keywords = new ArrayList<>();
또한 쿼리 최적화 과정에서 JPA 영속성 컨텍스트 문제가 발생하여
이를 양방향 매핑으로 해결했습니다.
이 부분의 기초가 부실한 것 같아 내일은 JPA 공식문서를 계속 살펴볼 예정입니다.
2. 유저 도메인 테스트 코드 작성
서비스와 레포지토리 단위 테스트를 작성하고,
통합 테스트또한 작성하였습니다.
이때 온보딩 시 LLM으로 유저 프로필을 생성하고, 임베딩 API로 임베딩한 후 색인하는 과정이 있어
@MockitoBean을 통해 각각을 모킹하였고,
MySQL뿐만 아니라 ES 또한 테스트 컨테이너를 띄어 진행하였습니다.
@SpringBootTest
@AutoConfigureMockMvc
@Import({MySQLTestConfig.class, ElasticsearchTestConfig.class})
@ActiveProfiles("integrationtest")
class OnboardingControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private UserRepository userRepository;
@Autowired
private ObjectMapper objectMapper;
@MockitoBean
private LlmClient llmClient;
@MockitoBean
private EmbeddingClient embeddingClient;
private User testUser;
@BeforeEach
void setUp() {
testUser = User.create();
testUser = userRepository.save(testUser);
// LLM 클라이언트 모킹 - 테스트용 더미 응답 반환
when(llmClient.call(anyString(), anyString()))
.thenReturn("테스트용 사용자 프로필입니다. 백엔드 개발에 관심이 많습니다.");
// Embedding 클라이언트 모킹 - 테스트용 더미 벡터 반환
when(embeddingClient.embed(anyString()))
.thenReturn(Collections.nCopies(3072, 0.1f));
}
@AfterEach
void tearDown() {
userRepository.deleteAll();
}
@Test
@DisplayName("관심사 목록 조회 - 성공")
void getInterests_Success() throws Exception {
// When & Then
mockMvc.perform(get("/api/v1/onboarding/interests"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(jsonPath("$.isSuccess").value(true))
.andExpect(jsonPath("$.data.categories").isArray())
.andExpect(jsonPath("$.data.categories").isNotEmpty());
}
@Test
@DisplayName("온보딩 완료 - 정상 케이스")
void completeOnboarding_Success() throws Exception {
// Given
List<UserInterestDto> interests = List.of(
UserInterestDto.builder()
.category("BACKEND")
.keywords(List.of("JAVA", "SPRING"))
.build(),
UserInterestDto.builder()
.category("DATABASE")
.keywords(List.of("MYSQL", "REDIS"))
.build()
);
OnboardingRequest request = new OnboardingRequest(
"테크포크유저",
"user@techfork.com",
"백엔드 개발자입니다",
interests
);
String requestBody = objectMapper.writeValueAsString(request);
// When & Then
mockMvc.perform(post("/api/v1/onboarding/complete")
.header("X-User-Id", testUser.getId())
.contentType(MediaType.APPLICATION_JSON)
.content(requestBody))
.andDo(print())
.andExpect(status().isCreated())
.andExpect(jsonPath("$.isSuccess").value(true));
// 데이터베이스 검증
User savedUser = userRepository.findByIdWithInterestCategories(testUser.getId()).orElseThrow();
assertThat(savedUser.getNickName()).isEqualTo("테크포크유저");
assertThat(savedUser.getEmail()).isEqualTo("user@techfork.com");
assertThat(savedUser.getDescription()).isEqualTo("백엔드 개발자입니다");
assertThat(savedUser.getInterestCategories()).hasSize(2);
}
// ...
3. 엔티티 생성 성능 최적화
Spring JPA에 대한 이해를 늘리기 위해 공식 문서를 살펴보던 중 엔티티 생성에서 전체 변수를 가진 생성자에 특정 어노테이션을 붙이면 생성 성능을 30% 향상할 수 있음을 파악했습니다.
https://docs.spring.io/spring-data/jpa/reference/data-commons/3.5/object-mapping.html
Object Mapping Fundamentals :: Spring Data JPA
This section covers the fundamentals of Spring Data object mapping, object creation, field and property access, mutability and immutability. Note, that this section only applies to Spring Data modules that do not use the object mapping of the underlying da
docs.spring.io
@PersistenceCreator를 생성자에 붙이고,
접근 제어자가 private면 접근할 수 없으므로 package-private로 변경하여 생성 성능 최적화를 도모했습니다.
감사합니다.
'프로젝트 > Techfork' 카테고리의 다른 글
| [26/01/20] 오늘의 개발 일지 - CI에서 테스트 실행 및 통합 테스트 컨테이너 환경 개선 (1) | 2026.01.20 |
|---|---|
| [26/01/14] 오늘의 개발 일지 - 카카오 소셜 로그인 구현 (2) | 2026.01.15 |
| [26/01/07] 오늘의 개발 일지 - Resilience4j 설정 개선 및 JdbcTemplate를 활용한 배치 처리 (0) | 2026.01.08 |
| [26/01/06] 오늘의 개발일지 - AsyncItemProcessor의 도입과 Spring Batch 메타데이터 활용 (0) | 2026.01.07 |
| [26/01/05] 오늘의 개발 일지 - Resilience4j 도입 (1) | 2026.01.06 |