【问题标题】:Cancel SQL Statement with JDBC使用 JDBC 取消 SQL 语句
【发布时间】:2015-02-12 15:49:44
【问题描述】:

我在这里遇到了这个问题。我正在我的 Tomcat 应用程序服务器上运行一个应用程序。作为前端,我使用的是一个带有 javascript 的 HTML 网站,在后端我使用的是 Java。

当用户单击一个按钮时,会进行多个 sql 查询,一个接一个。现在,如果用户愿意,我想提供取消此查询的功能。

我已经检查了我的 jdbc 驱动程序和数据库是否与cancel() 方法兼容,这很好。

这是我的代码:

PreparedStatement stmt = null;

public void runQuery(String query) {
    Connection con = getConnection();       

    try {
        stmt = con.prepareStatement(query);
        stmt.execute();
    } catch(SQLException e) {
        e.printStackTrace();
    } finally {
        if(stmt != null && !stmt.isClosed()) {
            stmt.close();
        }

        if(con != null) {
            con.close();
        }
    }
}

public void cancelQuery() {
    try {
        if(stmt != null && !stmt.isClosed()) {
            stmt.cancel();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

所以用户点击运行按钮 => runQuery 被执行,stmt 被初始化/覆盖为需要执行的查询。

然后用户点击取消按钮 => cancelQuery 被执行。 不幸的是,我有时会收到 NullPointerException,因为 stmt 为空。但如果 stmt 为 null ,它甚至不应该调用 cancelQuery ?! 这是堆栈跟踪:

    Stacktrace:] with root cause
java.lang.NullPointerException
    at com.sybase.jdbc3.jdbc.SybStatement.doCancel(SybStatement.java:646)
    at com.sybase.jdbc3.jdbc.SybStatement.cancel(SybStatement.java:614)
    at org.apache.tomcat.dbcp.dbcp2.DelegatingStatement.cancel(DelegatingStatement.java:269)
    at org.apache.tomcat.dbcp.dbcp2.DelegatingStatement.cancel(DelegatingStatement.java:269)
    at de.package.util.DBHelper.cancelQuery(DBHelper.java:82)
.....

知道为什么这会不断产生异常吗?我怎样才能以正确的方式取消声明?

编辑: 我已经查看了 cmets 中的链接,现在从不同的线程运行 cancel() 方法。然而 NullPointer 仍然发生。这就是我现在调用 cancel() 方法的方式:

public void cancelQuery() {
        Thread thread = new Thread(new SQLCancelRunnable(stmt));
        thread.start();
}

    public class SQLCancelRunnable implements Runnable {
    PreparedStatement stmt;

    public SQLCancelRunnable(PreparedStatement stmt) {
        this.stmt = stmt;
    }

    @Override
    public void run() {
        if(stmt != null) {
        try {
            System.out.println(stmt);
            System.out.println(stmt.toString());
                stmt.cancel();
                System.out.println("canceled");

        } catch (SQLException e) {
            e.printStackTrace();
        }

       }
   }
}

EDIT2 找到我的答案,问题是 runQuery() 方法的 finally 块。因为我关闭了语句和连接,所以抛出了 NullPointer。 我现在删除了这个块,但这当然会导致大量资源泄漏。谁能指导我正确地关闭我的资源?

【问题讨论】:

  • @NathanHughes 请看看我编辑的问题。该链接没有带来整体解决方案:(
  • 我会说,为什么要担心呢?只需捕获 NullPointerException。
  • 既然 NullPointerException 是一个运行时异常,我不应该抓住它而是避免它对吗?
  • @dehlen ,查看更新答案了解如何释放资源

标签: java sql tomcat jdbc


【解决方案1】:
    PreparedStatement stmt = null;

    public void runQuery(String query) {
    Connection con = getConnection();       


    try {
    stmt = con.prepareStatement(query);
    stmt.execute();

    }
    catch(SQLException e) {
        e.printStackTrace();
    }
    finally {
        if(stmt != null && !stmt.isClosed()) {
            stmt.close();
        }

        if(con != null) {
        con.close();
        }
    }

}

public void cancelQuery() {
    try {
        if(stmt != null && !stmt.isClosed()) {
            stmt.cancel();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

试试这个。我在 SQLException 之后添加了一个通用异常。

不能说这是一个非常干净的解决方案,但它会忽略可能由 stmt.close() 语句引发的空指针异常。

【讨论】:

  • 既然这是我现在采用的答案,我会将您标记为已接受,尽管它可能不是最干净的解决方案..
【解决方案2】:

你可以使用Statement.cancel()

正如 Java 文档所说

void cancel()
            throws SQLException

如果 DBMS 和驱动程序都支持,则取消此 Statement 对象 中止 SQL 语句。该方法可由一个线程使用 取消另一个线程正在执行的语句。

如果查询执行超过阈值时间,您也可以设置setQueryTimeout

java.sql.Statement.setQueryTimeout(seconds)

更新

别忘了Rollback交易

任何可以指导我正确方向的人如何关闭我的 资源正确吗?

这就是 finally 块的发明

finally{
//Release All Resources
}

finally 块总是在 try 块退出时执行。这 确保即使出现意外情况也会执行 finally 块 发生异常。但 finally 不仅仅对异常有用 处理——它允许程序员避免清理代码 意外地被返回、继续或中断绕过。进行清理 finally 块中的代码始终是一个好习惯,即使没有 预计会有例外情况。

【讨论】:

  • 感谢您的回答,但基本上您描述的是我已经尝试过的内容。我关闭了 finally 块中的所有资源,并从不同的线程运行 cancel() 方法。
【解决方案3】:

您需要同步语句关闭:

public void runQuery(String query) {
    ...
    try {
        stmt = con.prepareStatement(query);

        stmt.execute();
        ...
    finally {
        synchronized(this) {
            if(stmt != null && !stmt.isClosed()) {
                stmt.close();
            }
        }
    }
}

public void cancelQuery() {
    synchronized(this) {
        if(stmt != null && !stmt.isClosed()) {
            stmt.cancel();
        }
    }
}

在每个语句之间,另一个线程可能会执行某种代码,因此一个简单的 if 不足以确保世界的状态与您期望的一样。

this 上同步可能不是最佳选择,但由于stmt 可能为空,我们不能使用此对象。

编辑:如果启动查询的代码是异步的,您还必须准备好调用cancelQuery,甚至在您的语句准备好之前。

【讨论】:

    【解决方案4】:

    你应该看看Apache DB-Utils,它让这种问题消失了,你可以简单地写这样的东西:

    } finally {
        DbUtils.closeQuietly(resutlSet);
        DbUtils.closeQuietly(preparedStatement);
        DbUtils.closeQuietly(connection);
    }
    

    【讨论】:

    • 2021年这个还常用吗?还是同时开发了一些更现代的东西?
    • 从今天开始,应该将 try 与资源块一起使用,这可能仍然会抛出 SqlExceptions,但它们至少可以正确处理 try 或 finally 中引发的 NPE 和异常(记住永远不要在 finally 中抛出异常块?)
    猜你喜欢
    • 2015-05-29
    • 1970-01-01
    • 2014-12-16
    • 1970-01-01
    • 2017-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多