DB 조회 시 결과값이 없을 때
문제
사용자에게 입력 받은 파라미터를 DB 조회했을 때 데이터가 null, 정확히는 존재하지 않을 경우 해당 기능이 중단되었다.
서버 자체가 종료되는 것도 아니고 이렇다 할 Error log가 뜨는 것도 아니어서 무엇부터 건드려야 할 지 난감했다.
아래 글을 설명할 예시 코드는 아래와 같고, 사용한 프레임워크는 Spring과 MyBatis, DB는 MySQL이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
...
@RestController
public static class UserController {
@Autowired
private UserService userService;
@GetMapping("/id")
public Integer getUserId(@RequestParam String userName) {
return userService.getUserIdByUserName(userName);
}
}
@Service
public static class UserService {
@Autowired
private UserMapper userMapper;
public Integer getUserIdByUserName(String userName) {
return userMapper.selectUserId(userName);
}
}
@Mapper
public interface UserMapper {
@Select("SELECT id FROM user WHERE user_name = #{userName}")
Integer selectUserId(@Param("userName") String userName);
}
...
1
2
3
4
5
SELECT IFNULL(id, 0)
FROM user
WHERE user_name IN (#{userName});
원인
해당 기능은 새롭게 추가하는 기능어서 실질적으로 구현을 한 것은 SQL뿐이었다. 따라서 Spring 내에서의 문제는 아니라고 판단해 작성한 쿼리문을 기반으로 구글링을 시도했다.
파악
데이터가 존재하지 않을 경우, MySQL측에서는 아무런 값도 반환하지 못했다. 이 때문에 DB와의 연결이 비정상적으로 종료되는 것 같았다.
이를 처리하기 위해 IFNULL을 사용했는데 왜 오류가 나나 해서 확인했더니, IFNULL은 데이터가 존재하되, 해당 컬럼 값이 null일 경우에 사용하는 함수여서 데이터가 아예 존재하지 않을 경우에는 의미가 없었다.
실질적인 값으로 예를 들어보자.
id | user_name |
---|---|
1 | A |
2 | B |
null | C |
위와 같이 값이 있을 때 select ifnull(id, 100) from user where user_name in ('C');
를 하면 100이 결과값으로 출력되지만 select ifnull(id, 100) from user where user_name in ('D');
를 했을 때는 아무런 값도 조회되지 않는 것이다!
해결
이 경우에는 1. UNION 사용과 2. 집계 함수 사용으로 해결할 수 있다고 한다.
1. UNION 사용
1
2
3
4
5
6
SELECT IFNULL(id, 0)
FROM user
WHERE user_name in (#{userName})
UNION
SELECT 0
LIMIT 1;
위 예시처럼 UNION을 사용해 항상 0을 아래에 붙여 조회되도록 한다.
데이터가 존재할 경우에는 LIMIT에 의해 해당 데이터가 반환되지만, 데이터가 존재하지 않으면 UNION에 의해 추가된 0이 반환되도록 하여 데이터가 존재하지 않는 경우를 만들지 않는다.
2. 집계 함수 사용
1
2
3
SELECT IFNULL(MAX(id), 0)
FROM user
WHERE user_name in (#{userName});
만약 user_name이 D인 것을 조회한다고 가정해보자. MAX(id)는 null의 결과값을 가지게 되고, 이는 IFNULL을 통해 처리되어 최종적으로 0이 반환된다.
user_name이 C인 것을 조회한다면, 어차피 해당하는 id는 하나이기 때문에 문제 없이 돌아간다. (다만, 좋은 쿼리문이라고 할 수 없다. 이렇게 처리할 이유가 없으니까.)
회고
DB에서 값이 존재하지 않는 것과 NULL이 동일하게 처리될 것이라고 생각했다. NULL에 대한 정의를 알아가는 시간이 필요할 것 같다.