Java JDBC查询回调方法中SQLException统一处理方案咨询
问题根因
JDK自带的Consumer<T>接口的accept方法没有声明抛出任何受检异常,而你在lambda中调用的ResultSet.next()、ResultSet.getString()等方法都会抛出受检类型SQLException,和Consumer的方法签名不匹配,因此直接报错。哪怕外层方法声明了throws SQLException也没用,因为lambda本质是实现Consumer接口的方法,本身不允许抛出未声明的受检异常。
另外你原有代码存在资源泄漏风险:如果callback执行抛出异常,后续的connection.close()不会执行,数据库连接会一直被占用。
实现步骤
1. 自定义支持抛出SQLException的函数式接口
@FunctionalInterface public interface SQLConsumer<T> { void accept(T t) throws SQLException; }
该接口的方法签名明确允许抛出SQLException,可以直接匹配你回调逻辑中的异常场景。
2. 修改query方法入参类型,优化资源释放逻辑
用try-with-resources自动释放数据库连接、Statement、ResultSet资源,不需要手动写close逻辑,避免资源泄漏:
public void query(String query, SQLConsumer<ResultSet> callback, String database) throws SQLException { // 驱动加载仅需执行一次,建议挪到类的静态代码块中,不需要每次调用query都重复加载 try { Class.forName(DRIVER_CLASS); } catch (ClassNotFoundException e) { throw new RuntimeException("数据库驱动加载失败", e); } // try-with-resources会在代码块结束后自动按顺序释放所有资源,无论是否抛出异常 try (Connection connection = DriverManager.getConnection(URL + "/" + database, USER, ""); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(query)) { callback.accept(resultSet); } }
修改后你原有printUsernames的代码不需要做任何调整,就可以正常编译运行,异常会直接从query方法向上抛出。
可选:query内部统一处理异常
如果你希望所有查询的异常都在query内部处理,不需要外层方法声明throws SQLException,可以调整query逻辑如下:
public void query(String query, SQLConsumer<ResultSet> callback, String database) { try { Class.forName(DRIVER_CLASS); } catch (ClassNotFoundException e) { throw new RuntimeException("数据库驱动加载失败", e); } try (Connection connection = DriverManager.getConnection(URL + "/" + database, USER, ""); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(query)) { callback.accept(resultSet); } catch (SQLException e) { // 此处统一处理异常,比如打印日志、封装为自定义业务异常抛出 e.printStackTrace(); // 可选:throw new BusinessException("数据库查询失败", e); } }
调整后后续你写所有查询回调时,都不需要重复写try-catch块,也不需要外层方法声明抛出SQLException。
临时替代方案
如果不想自定义函数式接口,也可以在lambda内部把受检异常包装为运行时异常抛出:
public void printUsernames() throws SQLException { query("SELECT username FROM users;", (ResultSet set) -> { try { while (set.next()) { String username = set.getString("username"); System.out.println(username); } } catch (SQLException e) { throw new RuntimeException(e); } }, "cloud"); }
该方案需要每个回调都写重复的try-catch逻辑,不如自定义接口的方案简洁高效。
内容的提问来源于stack exchange,提问作者Edgar TA

