JdbcTemplate - query, queryForObject

Jang990·2026년 1월 26일

기존 코드를 내가 만든 JdbcTemplatequeryForObjectquery로 변경해볼 것이다.

일단 MyRowMapper를 제공하자.

@FunctionalInterface
public interface MyRowMapper<T> {
    // rowNum도 있다는데 여기선 안쓰니까 생략
    T mapRow(ResultSet rs/*, int rowNum*/) throws SQLException;
}

queryForObject

생성

    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);
        }
    }

NPE 발생 가능성

결과물이 없을 때 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);
        }
    }

query로 변경

queryForObjectList 버전이다.

생성

중간에 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);
        }
    }
profile
개발 기록 아카이브

0개의 댓글