1

I am stuck with this problem here. I am running an application on my Tomcat Application Server. As a frontend I am using an HTML site with javascript in it, in the backend i am using Java.

When the user clicks on a button several sql queries are made, one after another. Now I want to provide the ability to cancel this query if the user wants to.

I already checked if my jdbc driver and the database are compatible for the cancel() method and this is just fine.

Here is my code :

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();
    }
}

So the user clicks the run button => runQuery is executed and stmt is initialized/overriden with the query which needs to execute.

Then the user clicks the cancel button => cancelQuery is executed. Unfortunately I sometimes get a NullPointerException because stmt is null. But it should not even call cancelQuery if the stmt is null ?! Here is the stacktrace:

    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)
.....

Any idea why this keeps producing an exception ? How can I cancel the statement the right way ?

EDIT: I had allook at the link in the comments and now running the cancel() method from a different thread. However the NullPointer still happens. This is how i call the cancel() method now:

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 Found my answer the problem was the finally block of the runQuery() method. Because I closed statement & connection the NullPointer was thrown. I now removed this block but this, of course, leads to huge resource leaking. Anyone who can guide me in the right direction how to close my resources properly ?

dehlen
  • 7,325
  • 4
  • 43
  • 71
  • see http://stackoverflow.com/q/16589497/217324 – Nathan Hughes Feb 12 '15 at 16:52
  • @NathanHughes please have a look at my edited question. the link didn't brought the overall solution :( – dehlen Feb 13 '15 at 08:33
  • I would say, why worry about it? Just catch NullPointerException. – mwarren Feb 13 '15 at 08:54
  • Since NullPointerException is a runtime exception i should not catch it but avoid it right ? – dehlen Feb 13 '15 at 08:58
  • @dehlen , check out the updated answer to know how to release resources – Neeraj Jain Feb 13 '15 at 09:31
  • @dehlen: i didn't claim it would, otherwise i'd have closed this as a duplicate. – Nathan Hughes Feb 13 '15 at 13:16
  • @dehlen IHMO you should keep it simple! 1) Put back the finally clause which is absolutely essential because it releases the resources 2) In my opinion all that's happening is that in some cases, as you have discovered, the stmt is being closed right after the if(stmt != null) line. Keep it simple, forget other threads, which are complicated, just catch the NullPointerException and return. – mwarren Feb 13 '15 at 13:29
  • well ok I'll go with this answer. It looks like its working for me now.. Just tried to write correct code and not just "code that seems to work". But for now this will be the solution I'll take. – dehlen Feb 13 '15 at 13:32
  • suspect race condition in the "close vs. cancel" methods if one closes at "about the same time" as cancel is called some weirdness could happen. That NPE is not "in your code" it seems to be "in the heart of their code" when you call cancel. Also `con` is guaranteed to never be null. Cheers! – rogerdpack Sep 18 '19 at 14:33

4 Answers4

2
    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();
    }
}

Try this. I have added a Generic Exception just after the SQLException.

Cannot say this is a very clean solution but it will ignore the null pointer exception which is possiblly raised by stmt.close() statement.

Charles Brown
  • 917
  • 2
  • 10
  • 20
  • Since this is kind of the answer I go with now, I'll mark you as accepted, although It might not be the cleanest solution.. – dehlen Feb 13 '15 at 13:33
  • For anyone finding this question later: Please do not write code like this! Take look at the [answer by user "dag"](https://stackoverflow.com/a/58117608/373074) further down in this thread instead. – RolKau Jan 27 '23 at 08:20
2

You'll need to synchronize statement closing:

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();
        }
    }
}

between every statement another thread may execute some kind of code, so a simple if is not sufficient to be sure the state of the world is as you expect it to be.

Synchronizing on this may not be the best option, but as the stmt may be null we can't use this object.

Edit: If the code starting the query is asynchronous you'll also have to be prepared for calls to cancelQuery even before your statement is prepared.

dag
  • 1,133
  • 12
  • 25
1

You can use Statement.cancel()

As Java Docs Says

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.

You can also set setQueryTimeout if query execution passes a threshhold time

java.sql.Statement.setQueryTimeout(seconds)

Update

Don't forget to Rollback the transation

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

this is what for finally block was invented

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.

Neeraj Jain
  • 7,643
  • 6
  • 34
  • 62
  • 1
    thanks for your answer, but basically you are describing what I already tried. I close all my resources in the finally block and I run the cancel() method from a different thread. – dehlen Feb 13 '15 at 13:21
0

You should take a look at Apache DB-Utils it makes this kind of problems disappear and you can simply write something like:

} finally {
    DbUtils.closeQuietly(resutlSet);
    DbUtils.closeQuietly(preparedStatement);
    DbUtils.closeQuietly(connection);
}
Tulio C
  • 96
  • 1
  • 3
  • Is this still commonly used in 2021? Or has there something more modern developed in the meanwhile? – ShadowGames May 18 '21 at 14:58
  • as of today one should use try with resources blocks, which may still throw SqlExceptions but they at least handle the NPEs and Exceptions raised inside the try or in the finally correctly (remember never to throw exceptions in finally blocks?) – dag Jun 01 '21 at 09:14