기존 코드를 내가 만든 JdbcTemplate의 queryForObject와 query로 변경해볼 것이다.
@FunctionalInterface
public interface MyRowMapper<T> {
// rowNum도 있다는데 여기선 안쓰니까 생략
T mapRow(ResultSet rs/*, int rowNum*/) throws SQLException;
}
public <T> T queryForObject(Connection conn, String sql, MyRowMapper<T> mapper, Object... params) {
try(PreparedStatement ps = conn.prepareStatement(sql)) {
setArgsInPreparedStatement(ps, params); // ps에 setObject()
List<T> result = new LinkedList<>();
try(ResultSet rs = ps.executeQuery()) {
boolean hasData = rs.next();
if(hasData)
return mapper.mapRow(rs);
else
return null;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
결과물이 없을 때 return null을 하기 때문에
MyJdbcTemplate을 사용하는 코드에서 이걸 모르면 NPE가 터질 수도 있다.
단순하게 커스텀 예외를 던지는 방식으로 해결한다.
// 예외 생성
public class EmptyResultException extends RuntimeException { }
// 변경 후) null 대신 예외를 던진다.
if(hasData)
return mapper.mapRow(rs);
else
throw new EmptyResultException();
public Foods findById(long id) {
try (
Connection conn = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword());
PreparedStatement ps = conn.prepareStatement("""
SELECT id, name, price, stock
FROM foods
WHERE id = ?
""")
) {
ps.setLong(1, id);
try(ResultSet rs = ps.executeQuery()) {
boolean hasFood = rs.next();
if(!hasFood)
throw new IllegalArgumentException("음식을 찾을 수 없습니다. userId=%d".formatted(id));
return createFood(rs);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
private static final MyRowMapper<Foods> foodRowMapper = (rs) -> {
long foodId = rs.getLong(1);
Foods result = new Foods(rs.getString(2), rs.getInt(3), rs.getInt(4));
MyEntityIdInjector.injectId(result, foodId);
return result;
};
public Foods findById(long id) {
try (Connection conn = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword())) {
try {
return myJdbcTemplate.queryForObject(conn, """
SELECT id, name, price, stock
FROM foods
WHERE id = ?
""", foodRowMapper, id);
} catch(EmptyResultException e) {
throw new IllegalArgumentException("음식을 찾을 수 없습니다. userId=%d".formatted(id), e);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
queryForObject의 List 버전이다.
중간에 while문만 하나 들어가고 List로 반환하면 끝이다.
public <T> List<T> query(Connection conn, String sql, MyRowMapper<T> mapper, Object... params) {
try(PreparedStatement ps = conn.prepareStatement(sql)) {
setArgsInPreparedStatement(ps, params);
List<T> result = new LinkedList<>();
try(ResultSet rs = ps.executeQuery()) {
while (rs.next())
result.add(mapper.mapRow(rs));
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public List<Foods> findAll(List<Long> ids) {
if(ids.isEmpty())
throw new IllegalArgumentException("음식 검색 시 ids는 필수");
try (
Connection conn = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword());
PreparedStatement ps = conn.prepareStatement(createFindAllSql(ids.size()))
) {
for (int i = 0; i < ids.size(); i++)
ps.setLong(i + 1, ids.get(i));
try(ResultSet rs = ps.executeQuery()) {
List<Foods> foods = new LinkedList<>();
while (rs.next()) {
foods.add(createFood(rs));
}
return foods;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public List<Foods> findAll(List<Long> ids) {
if(ids.isEmpty())
throw new IllegalArgumentException("음식 검색 시 ids는 필수");
try (Connection conn = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword())) {
return myJdbcTemplate.query(conn, createFindAllSql(ids.size()), foodRowMapper, ids.toArray());
} catch (SQLException e) {
throw new RuntimeException(e);
}
}