Backend/Java

JDBC query vs queryForObject

비비빅B 2021. 3. 30. 01:09

서론

Spring JDBC를 연습하던 중에 단일 행을 조회하는 데 query를 사용하는 예시를 봤다. query는 List 객체를 반환해 굳이 단일 행을 조회하는 데 query를 쓸 필요가 있나? 하고 queryForObject를 써서 바로 단일 객체로 받아왔다.

 

정상적으로 작동했고 나름 뿌듯해하면서 예외상황에 대한 테스트 코드를 작성했다.

 

서비스에서 email를 찾지 못할 시 NotFoundException(404)를 던지게 코드를 작성했다.

 

@Transactional
public User login(Email email, String password) {
  checkNotNull(password, "password must be provided.");

  User user = findByEmail(email)
  	.orElseThrow(() -> new NotFoundException(User.class, email));
  user.login(passwordEncoder, password);
  user.afterLoginSuccess();
  update(user);
  return user;
}

DB에서 email로 회원을 찾지 못 할때 404 status ERROR가 발생할 것을 예상하는 테스트코드를 작성했다.

@Test
@DisplayName("[로그인] 없는 이메일 요청, NotFound 받음")
@Order(6)
void login_whenUserLoginWithEmailNotExist_receiveNotFound() {
  AuthenticationRequest authRequest = createValidAuthRequest();
  authRequest.setPrincipal("anonymous@gmail.com");

  ResponseEntity<ApiResult<Object>> response = postSignin(authRequest, new ParameterizedTypeReference<ApiResult<Object>>() {});

  assertThat(response.getBody().isSuccess()).isFalse();
  assertThat(response.getBody().getError().getStatus()).isEqualTo(HttpStatus.NOT_FOUND.value());
}

 

404에러를 예상했지만 500 에러가 발생했다. 에러코드를 살펴보니 DB조회 result 사이즈가 0이어서 오류를 던져버렸다.

org.springframework.dao.EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

음... 난 UserService에서 일치하는 회원이 없을 경우 404 에러를 발생시키게 했는데 왜 500에러가 발생했지?


queryForObject

 

메소드를 살펴보니 queryForObject는 결과로 단일 행을 받는 대신, 만약 0 또는 1 초과의 행이 반환되면 에러를 던진다.

0일 경우 EmptyResultDataAccessException, 2개 이상일 경우 IncorrectResultSizeDataAccessException발생한다.

 

이는 DB에러인 DataAccessException의 상속을 받으며, 500 서버에러로 간주된다.

@Override
public Optional<User> findByEmail(Email email) {
  User user = jdbcTemplate.queryForObject(
  	"SELECT * FROM users WHERE email=?",
  	mapper,
  	new Object[] {email.getAddress()});
  return Optional.ofNullable(user);
}

Optional을 사용해서 null일 경우에는 empty Optional을 반환하게 작성했는데, 애초에 null이 반환되기 전에 queryForObject에서 500 에러가 발생하는 것이었다.


query

반면, query 메소드는 List를 반환하는 대신 쿼리문 실행이 실패할 경우에만 DataAccessException을 던진다.

 

물론, queryForObject를 사용하면서 위의 두 에러(EmptyResultDataAccessException, IncorrectResultSizeDataAccessException)을 따로 핸들링해서 원하는 오류 상태를 만들 수 있지만 번거롭기도 하고 무엇보다 이메일을 잘못 입력해서 발생하는 오류는 DB 오류를 발생시키는 것보다,

Service 클라이언트 오류로 발생시키는 것이 좋은 방향인 것 같아 query로 바꿨다.

 

예시가 query문을 사용한 데는 마땅한 이유가 있는 것이었다.

@Override
public Optional<User> findByEmail(Email email) {
  List<User> results = jdbcTemplate.query(
    "SELECT * FROM users WHERE email=?",
    mapper,
    new Object[] {email.getAddress()});
  return Optional.ofNullable(results.isEmpty() ? null : results.get(0));
}

결론

단일 행을 조회하더라도,

사용자의 잘못으로 0이나 2개 이상의 행이 반환될 가능성이 있는 쿼리문은 query 메소드를 사용하는 것이 좋다.

 

'Backend > Java' 카테고리의 다른 글

SpringBoot profile logback  (0) 2021.06.24
Java Stream과 Multi Thread  (2) 2021.04.17
Spring Security JWT Token  (0) 2021.03.22
@RequestBody 모델에 기본생성자, setter/getter가 필요한가?  (4) 2021.01.21
parse, valueOf 차이와 문제점  (0) 2021.01.21