JDBC: кто закроет PreparedStatement, если executeQuery бросит исключение

Дают метод, который получает текущее время сервера БД через JDBC. Кандидат пишет рабочий код — и проваливается на трёх уточняющих вопросах.

public LocalDateTime getServerTime(Connection conn) throws SQLException { PreparedStatement ps = conn.prepareStatement( "SELECT NOW()"); ResultSet rs = ps.executeQuery(); if (rs.next()) { return rs.getTimestamp(1).toLocalDateTime(); } return null; }

Код компилируется и даже работает — пока не наступит исключение между prepareStatement и возвратом результата. Тогда ResultSet и PreparedStatement останутся открытыми: оба метода объявлены как throws SQLException, значит оба могут бросить исключение в середине выполнения.

Закрыть ресурсы в finally вручную — рабочий, но устаревший подход: легко забыть проверку на null, легко перепутать порядок закрытия. try-with-resources делает это автоматически и в правильном порядке — сначала ResultSet, потом PreparedStatement.

public LocalDateTime getServerTime(Connection conn) throws SQLException { String sql = "SELECT NOW()"; try (PreparedStatement ps = conn.prepareStatement(sql); ResultSet rs = ps.executeQuery()) { if (rs.next()) { return rs.getTimestamp(1).toLocalDateTime(); } return null; } }

Отдельный вопрос, который задают почти всегда: зачем здесь if (rs.next()), если запрос без условий гарантированно вернёт одну строку? Ответ — защитное программирование: даже при «гарантированном» результате код не должен падать с NoSuchElementException, если контракт запроса вдруг изменится. А ещё спросят про плейсхолдер: в стандартном JDBC это знак вопроса ?, без имён параметров — это уже NamedParameterJdbcTemplate из Spring.

Простой метод на время сервера — удобный способ проверить сразу три привычки: ресурсы, исключения и защитные проверки.

Тренажёр: 900+ вопросов и задач, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки