使用 JDBC 取消 SQL 语句

Posted

技术标签:

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

我在这里遇到了这个问题。我正在我的 Tomcat 应用程序服务器上运行一个应用程序。作为前端,我使用的是一个带有 javascripthtml 网站,在后端我使用的是 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。 我现在删除了这个块,但这当然会导致大量资源泄漏。谁能指导我正确地关闭我的资源?

【问题讨论】:

见***.com/q/16589497/217324 @NathanHughes 请看看我编辑的问题。该链接没有带来整体解决方案:( 我会说,为什么要担心呢?只需捕获 NullPointerException。 既然 NullPointerException 是一个运行时异常,我不应该抓住它而是避免它对吗? @dehlen ,查看更新答案了解如何释放资源 【参考方案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 中抛出异常块?)

以上是关于使用 JDBC 取消 SQL 语句的主要内容,如果未能解决你的问题,请参考以下文章

java中怎么执行sql语句

Spark SQL - 使用 SQL 语句使用 JDBC 加载数据,而不是表名

JDBC_批量处理SQL语句

java jdbc 执行sql语句批量操作问题

java jdbc sql语句参数的设置

在JDBC中使用带参数的SQL语句