使用 JDBC 取消 SQL 语句

Cancel SQL Statement with JDBC

我在这里遇到了这个问题。我正在 运行 在我的 Tomcat 应用程序服务器上安装一个应用程序。作为前端,我使用 HTML 站点,其中包含 javascript,在后端,我使用 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是initialized/overriden需要执行的查询。

然后用户点击取消按钮=> cancelQuery被执行。 不幸的是,我有时会收到 NullPointerException,因为 stmt 为空。但如果 stmt 为空,它甚至不应该调用 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)
.....

知道为什么这会一直产生异常吗?我怎样才能正确取消声明?

编辑: 我已经查看了评论中的 link,现在 运行 从另一个线程中调用了 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 找到我的答案,问题是 运行Query() 方法的 finally 块。因为我关闭了语句和连接,所以抛出了 NullPointer。 我现在删除了这个块,但这当然会导致巨大的资源泄漏。任何人都可以指导我正确的方向如何正确关闭我的资源?

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

} finally {
    DbUtils.closeQuietly(resutlSet);
    DbUtils.closeQuietly(preparedStatement);
    DbUtils.closeQuietly(connection);
}
    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() 语句可能引发的空指针异常。

您可以使用 Statement.cancel()

如 Java 文档所述

void cancel()
            throws SQLException

Cancels this Statement object if both the DBMS and driver support aborting an SQL statement. This method can be used by one thread to cancel a statement that is being executed by another thread.

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

java.sql.Statement.setQueryTimeout(seconds)

更新

别忘了Rollback 翻译

Anyone who can guide me in the right direction how to close my resources properly ?

finally 块就是这样发明的

finally{
//Release All Resources
}

The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break. Putting cleanup code in a finally block is always a good practice, even when no exceptions are anticipated.

您需要同步语句关闭:

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,甚至在您的语句准备好之前。