@Transactional public SignupResponse signup(SignupRequest signupRequest) { String encodedPassword = passwordEncoder.encode(signupRequest.getPassword()); UserRole userRole = UserRole.of(signupRequest.getUserRole()); if (userRepository.existsByEmail(signupRequest.getEmail())) { throw new InvalidRequestException("이미 존재하는 이메일입니다."); } User newUser = new User( signupRequest.getEmail(), encodedPassword, userRole ); User savedUser = userRepository.save(newUser); String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), userRole); return new SignupResponse(bearerToken); }
if (userRepository.existsByEmail(signupRequest.getEmail())) {
throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}
해당 에러가 발생하는 상황일 때, passwordEncoder의 encode() 동작이 불필요하게 일어나지 않게 코드를 개선해주세요.
답
@Transactional public SignupResponse signup(SignupRequest signupRequest) { if (userRepository.existsByEmail(signupRequest.getEmail())) { throw new InvalidRequestException("이미 존재하는 이메일입니다."); } String encodedPassword = passwordEncoder.encode(signupRequest.getPassword()); UserRole userRole = UserRole.of(signupRequest.getUserRole()); User newUser = new User( signupRequest.getEmail(), encodedPassword, userRole ); User savedUser = userRepository.save(newUser); String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), userRole); return new SignupResponse(bearerToken); }
WeatherDto[] weatherArray = responseEntity.getBody(); if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) { throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode()); } else { if (weatherArray == null || weatherArray.length == 0) { throw new ServerException("날씨 데이터가 없습니다."); } }
복잡한 if-else 구조는 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만듭니다. 불필요한 else 블록을 없애 코드를 간결하게 합니다.
답:
WeatherDto[] weatherArray = responseEntity.getBody();
if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode());
}
if (weatherArray == null || weatherArray.length == 0) {
throw new ServerException("날씨 데이터가 없습니다.");
}
if (userChangePasswordRequest.getNewPassword().length() < 8 || !userChangePasswordRequest.getNewPassword().matches(".*\\d.*") || !userChangePasswordRequest.getNewPassword().matches(".*[A-Z].*")) { throw new InvalidRequestException("새 비밀번호는 8자 이상이어야 하고, 숫자와 대문자를 포함해야 합니다."); }
패키지 package org.example.expert.domain.user.service; 의 UserService 클래스에 있는 changePassword() 중 아래 코드 부분을 해당 API의 요청 DTO에서 처리할 수 있게 개선해주세요.
답
UserChangePasswordRequest
@NotBlank @Pattern( regexp = "^(?=.*[A-Z])(?=.*\\d).{8,}$", message = "새 비밀번호는 8자 이상이어야 하고, 숫자와 대문자를 포함해야 합니다." ) private String newPassword;
UserController
@PutMapping("/users") public void changePassword(@Auth AuthUser authUser, @RequestBody @Valid UserChangePasswordRequest userChangePasswordRequest) { userService.changePassword(authUser.getId(), userChangePasswordRequest); }
TodoRepository가 있습니다. 해당 Repository가 어떤 기능을 활용해서 N+1을 해결하고 있는지 분석 해보세요.@Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC") Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);
| id | title | user |
|---|---|---|
| 1 | TIL 작성 | 5 |
| 2 | 코드카타 하기 | 2 |
| 3 | 강의 다 듣기 | 5 |
위에 Todo데이트를 user 정보랑 같이 가져오는거
LEFT JOIN FETCH
| 단어 | 뜻 |
|---|---|
| LEFT JOIN | Todo에 user가 없어도 Todo는 가져온다. |
| FETCH | User도 같이 한 번에 가져오기 |
| t.user | Todo가 갖고 있는 user정보 |
ORDER BY t.modifiedAt DESC
| 메서드 | 의미 |
|---|---|
| findAllBy | 전체 다 가져옵니다. |
| OrderBy....Desc | modifiedAt 기준으로 내림차순 정렬(최근 → 오래된 순) |
(Pageable pageable)
@Query("SELECT t FROM Todo t " + "LEFT JOIN FETCH t.user " + "WHERE t.id = :todoId") Optional<Todo> findByIdWithUser(@Param("todoId") Long todoId);
LEFT JOIN FETCH t.user
Todo와 연결된 user도 한번에 같이 가져온다
WHERE t.id = :todoId
특정 id 값 하나만 조회
:todoId는 파라미터로 넘기는 값
Optional< Todo >
Todo가 있을 수 있고 없을 수 있어서 Optional로 감싼 것
이를 동일한 동작을 하는 @EntityGraph 기반의 구현으로 수정해주세요.
@EntityGraph
필요한 데이터를 한 번에 같이 가져오게 하는 도구
@EntityGraph를 안쓰면 Todo를 가져온 다음에 다시 Todo에 연결된 user를 하나씩 따로 가져와야함(지연로딩 = N + 1 문제 발생)
@EntityGraph를 쓰면 Todo랑 연결된 user를 동시에 가져옴
@EntityGraph로 구현하면
@EntityGraph(attributePaths = {"user"}) Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable); @EntityGraph(attributePaths = {"user"}) Optional<Todo> findById(Long todoId);
테스트 패키지 package org.example.expert.config; 의 PassEncoderTest 클래스에 있는 matches메서드가정상적으로_동작한다() 테스트가 의도대로 성공할 수 있게 수정해 주세요.
@ExtendWith(SpringExtension.class)
class PasswordEncoderTest {@InjectMocks private PasswordEncoder passwordEncoder; @Test void matches_메서드가_정상적으로_동작한다() { // given String rawPassword = "testPassword"; String encodedPassword = passwordEncoder.encode(rawPassword); // when boolean matches = passwordEncoder.matches(encodedPassword, rawPassword); // then assertTrue(matches);
답
passwordEncoder.matches(encodedPassword, rawPassword);
이부분에서 자물쇠(암호화된 비밀번호) 에 열쇠(내가 입력한 비밀번호) 를 넣어야 열리는데 위에 코드처럼 하면 열쇠를 자물쇠로 착각하고 자물쇠로 열쇠를 열려는 상황이여서
boolean matches = passwordEncoder.matches(rawPassword, encodedPassword);
위에 처럼 바꾸면 된다.
테스트 패키지 package org.example.expert.domain.manager.service; 의 ManagerServiceTest 의 클래스에 있는 manager_목록_조회_시_Todo가_없다면_NPE_에러를_던진다() 테스트가 성공하고 컨텍스트와 일치하도록 테스트 코드와 테스트 코드 메서드 명을 수정해 주세요.
던지는 에러가 NullPointerException이 아니므로 메서드명 또한 수정되어야 해요!
public void manager_목록_조회_시_Todo가_없다면_NPE_에러를_던진다() { // given long todoId = 1L; given(todoRepository.findById(todoId)).willReturn(Optional.empty()); // when & then InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> managerService.getManagers(todoId)); assertEquals("Manager not found", exception.getMessage()); }
답
InvalidRequestException exception = assertThrows(InvalidRequestException.class, () -> managerService.getManagers(todoId)); assertEquals("Manager not found", exception.getMessage());
테스트 패키지 package org.example.expert.domain.manager.service; 의 ManagerServiceTest 의 클래스에 있는 manager목록조회시_Todo가없다면NPE에러를_던진다() 테스트가 성공하고 컨텍스트와 일치하도록 테스트 코드와 테스트 코드 메서드 명을 수정해 주세요.
답
// 기존
public void manager목록조회시_Todo가없다면NPE에러를_던진다()// 변경
public void manager목록조회시_Todo가없다면예외가발생한다()
테스트 패키지 org.example.expert.domain.comment.service; 의 CommentServiceTest 의 클래스에 있는 comment등록중할일을찾지못해에러가_발생한다() 테스트가 성공할 수 있도록 테스트 코드를 수정해 주세요.
답
// 기존
assertEquals("Todo not found", exception.getMessage());// 변경
Todo todo = todoRepository.findById(todoId)
.orElseThrow(() -> new ServerException("Todo not found"));
테스트 패키지 org.example.expert.domain.manager.service의 ManagerServiceTest 클래스에 있는 todo의user가_null인경우예외가발생한다() 테스트가 성공할 수 있도록 서비스 로직을 수정해 주세요.
답
org.example.expert.service.MaanagerService
if (todo.getUser() == null) {
throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다.");
}
아니 이게 산삼보다 귀하다는 글인가요