1

我本质上是在构建一个 java 应用程序来处理和响应 RPC 事件。我发现自己一直在做以下事情,而我的 Java 知识正在碰壁。

        PreparedStatement preparedStatement = null;

        try {
            preparedStatement = conn.prepareStatement(removeFollowersStmt);
            preparedStatement.setLong(1, Long.parseLong(conversation) );
            preparedStatement.setLong(2, Long.parseLong(userId) );
            preparedStatement.executeUpdate();

            return true;
        } catch (SQLException e) {
            e.printStackTrace();
            return false;
        }
        finally
        {
            try {
                assert preparedStatement != null;
                preparedStatement.clearParameters();
                preparedStatement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

理想情况下,我只会执行一次 try/catch/finally,并且能够从 try 中调用各种数据库交互。

我对 java 不够熟悉,无法做到这一点,但我的想法是我可能可以创建一个接受闭包的函数,该闭包将在 try 中调用?

4

2 回答 2

3

看起来您已经发现对需要关闭的事物(例如 JDBC Statement)进行正确的异常处理是一件非常痛苦的事情。Java 8 lambdas 在这里可能会有所帮助,但这是 Java 7 中引入的try-with-resources 语句的教科书示例。事实上,该教程有一个 JDBC 示例,尽管它与您的示例有点不同'正试图在这里做。

在我们可以应用 try-with-resources 之前,我们需要仔细查看您的finally块。preparedStatement != null首先,在这一点上实际上是不正确的断言。conn.prepareStatementtry 块顶部的语句可以 throw beforeSQLExceptionpreparedStatement分配,因此在 finally 块执行时它实际上可能仍然为 null。(大多数人在这里添加一个空检查,并且只在它不为空时关闭该语句。) try-with-resources 语句通过在其 finally-block 负责关闭资源的 try-finally 语句之外初始化资源来避免这个问题.

clearParameters其次,在断言之后有一个调用。我不认为这是必要的。该语句即将关闭,并且preparedStatement变量即将超出范围,因此它将变得无法访问并因此被垃圾收集。清除参数应该不会有任何效果。

考虑到这些点,很明显 finally 块的主要职责是关闭语句,处理SQLException来自关闭操作的任何语句,允许封闭方法正常返回。这几乎就是 try-with-resources 所做的。

重写代码以使用 try-with-resources 给出以下结果:

    try (PreparedStatement preparedStatement = conn.prepareStatement(removeFollowersStmt)) {
        preparedStatement.setLong(1, Long.parseLong(conversation) );
        preparedStatement.setLong(2, Long.parseLong(userId) );
        preparedStatement.executeUpdate();
        return true;
    } catch (SQLException e) {
        e.printStackTrace();
        return false;
    }

没错,整个finally-block都可以丢掉!但是,确切的行为与您的原始代码有些不同。不同之处在于如果一切都通过executeUpdate成功,但关闭语句会抛出一个SQLException. 在原始代码中,将打印堆栈跟踪并且该方法将返回true

在修改后的代码中,close调用的异常将被此处的单个 catch 子句捕获,该子句将打印堆栈跟踪并返回false。我不知道这是否正确。我的印象是,如果关闭语句引发异常,则可能意味着先前执行的更新实际上并未成功。如果是这样,那么返回false是正确的事情。(但我不是 JDBC 专家。)

这比以前更好,但仍然需要在每个语句执行周围添加 try-catch 样板。你能重用这个结构并传入一个lambda吗?我想是的,但我们必须先做一些准备。该方法将需要一个用于创建 的 SQL 字符串PreparedStatement和一个负责将参数设置到语句中的 lambda。这里的问题是所有人的二传手PreparedStatement都可以 throw SQLException。Java 8 包中的内置函数式接口都没有java.util.function处理这个问题,所以我们必须创建自己的函数式接口:

interface StatementPreparer {
    void prepare(PreparedStatement ps) throws SQLException;
}

现在我们有了这个,让我们编写一个方法来准备一个语句并执行它,处理异常,并返回一个布尔状态:

boolean update(String sql, StatementPreparer sp) {
    try (PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
        sp.prepare(preparedStatement);
        preparedStatement.executeUpdate();
        return true;
    } catch (SQLException e) {
        e.printStackTrace();
        return false;
    }
}

现在在想要执行实际工作的代码中,它可以发出如下调用:

boolean result = update("delete from followers where conv = ? and userid = ?",
    preparedStatement -> {
        preparedStatement.setLong(1, Long.parseLong(conversation) );
        preparedStatement.setLong(2, Long.parseLong(userId) );
    });
于 2014-07-18T05:10:47.863 回答
1

我猜这是一个 Web 应用程序,因为“Followers”这个词。以下内容也适用于独立应用程序,但会有所不同。

您的代码存在两个概念性问题,这使您的生活变得比实际需要的更加艰难:

1.您试图在错误的地方处理异常。

如果你发现自己正在编写如下代码:

try {
    doSomething()
} catch( SomeException e ){
    e.printStackTrace()
}

那么您的程序的“布局”有问题。如果您将异常处理移到调用链的某个位置,您会好得多。

如果这是一个 Web 应用程序,请在 servlet 中执行。所以在你的情况下,这将是:

void executeMyStatements throws SQLException {
    try (PreparedStatement preparedStatement = conn.prepareStatement(removeFollowersStmt))
        preparedStatement.setLong(1, Long.parseLong(conversation) );
        preparedStatement.setLong(2, Long.parseLong(userId) );
        preparedStatement.executeUpdate();
    }
    return true;
}

在某处

MyServlet extends HttpServlet {

    void doGet( ... ){

        try {
             executeMyStatements()
             doSomeMoreStuff()
             executeMyOtherStatements()

        } catch( Throwable t ){

             doSomethingMeaningfullWithException( t );
             // e.g. t.printStackTrace( response.getOutputStream() );
             // logger.error( t ); ...
        }
    }
}

2.使用交易

(也许你无论如何都在这样做,但以防万一)

交易会帮助你。如果前一个语句因您捕获的异常而失败,您真的确定继续执行下一个语句是有意义的吗?或者您的数据库是否处于您最好从您想做的事情重新开始的状态?

这与在其他地方处理异常的方法非常吻合。您基本上想要做的是:(伪代码

try{ 
    Connection con = createConnectionSomehow();
    Transaction transaction = con.startTransactionSomehow();

    executeSomeStatements();
    doStuff()
    doDeeperStuffWithMoreStatementsWhichCallOtherStatementsDeepBelowInMoria();

} catch( Exception t ){

    transaction.rollback();
    doSomethingUsefullWith( t );

} finally {

    transactions.commit();
    connection.close()
}

为了更清楚地展示主要概念,您将不得不在我省略的部分中包含一些语句,finallyif != null应该不难。

哦,顺便说一句:您正在使用parseLong创建您的声明。这将在某一天或另一天失败NumberFormatException,因为它是一个RuntimeException不需要被捕获的。在您的方法中,您只处理 SQLException ,因此另一个将向上传播并且(在最坏的情况下,在独立应用程序中)将使应用程序崩溃。如果你使用我的方法,这个异常会被捕获catch( Throwable t )并将被优雅地处理。

以建议的方式组织代码将使您的生活更轻松,并避免您不喜欢的代码重复,同时提高总体代码质量和稳定性。如果出现问题,它将为您提供更好的错误处理。

于 2014-07-18T05:58:46.513 回答