본문 바로가기

프로젝트/Techfork

[26/01/10] 오늘의 개발일지 - 온보딩 API 수정, 테스트 코드 작성, 엔티티 생성 성능 최적화

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로 변경하여 생성 성능 최적화를 도모했습니다.

 

 

감사합니다.