如何使用 Spring 和 JDBCTemplate 取消长时间运行的查询?

发布于 2024-07-25 20:09:27 字数 403 浏览 14 评论 0原文

JDBC java.sql.Statement 类有一个 cancel() 方法。 可以在另一个线程中调用它来取消当前正在运行的语句。

我如何使用 Spring 来实现这一目标? 我找不到在运行查询时获取对语句的引用的方法。 我也找不到类似取消的方法。

这是一些示例代码。 想象一下这需要长达 10 秒的时间来执行,有时根据用户的请求,我想取消它:

    final int i = simpleJdbcTemplate.queryForInt("select max(gameid) from game");

我如何修改它以便我拥有对 java.sql.Statement 对象的引用?

The JDBC java.sql.Statement class has a cancel() method. This can be called in another thread to cancel a currently running statement.

How can I achieve this using Spring? I can't find a way to get a reference to a statement when running a query. Nor can I find a cancel-like method.

Here's some sample code. Imagine this takes up to 10 seconds to execute, and sometimes on the user's request, I want to cancel it:

    final int i = simpleJdbcTemplate.queryForInt("select max(gameid) from game");

How would I modify this so I have a reference to a java.sql.Statement object?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

长伴 2024-08-01 20:09:27

让我简化一下 oxbow_lakes 的答案:您可以使用查询方法的 PreparedStatementCreator 变体来访问该语句。

因此,您的代码:

final int i = simpleJdbcTemplate.queryForInt("select max(gameid) from game");

应该变成:

final PreparedStatement[] stmt = new PreparedStatement[1];
final int i = (Integer)getJdbcTemplate().query(new PreparedStatementCreator() {
    public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
        stmt[0] = connection.prepareStatement("select max(gameid) from game");
        return stmt[0];
    }
}, new ResultSetExtractor() {
    public Object extractData(ResultSet resultSet) throws SQLException, DataAccessException {
        return resultSet.getString(1);
    }
});

现在要取消,您只需调用

stmt[0].cancel()

您可能希望在实际运行查询之前向其他线程提供对 stmt 的引用,或者只是将其存储为成员变量。 否则,你真的无法取消任何事情......

Let me simplify oxbow_lakes's answer: you can use the PreparedStatementCreator variant of the query method to gain access to the statement.

So your code:

final int i = simpleJdbcTemplate.queryForInt("select max(gameid) from game");

Should turn into:

final PreparedStatement[] stmt = new PreparedStatement[1];
final int i = (Integer)getJdbcTemplate().query(new PreparedStatementCreator() {
    public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
        stmt[0] = connection.prepareStatement("select max(gameid) from game");
        return stmt[0];
    }
}, new ResultSetExtractor() {
    public Object extractData(ResultSet resultSet) throws SQLException, DataAccessException {
        return resultSet.getString(1);
    }
});

Now to cancel you can just call

stmt[0].cancel()

You probably want to give a reference to stmt to some other thread before actually running the query, or simply store it as a member variable. Otherwise, you can't really cancel anything...

满天都是小星星 2024-08-01 20:09:27

您可以通过 JdbcTemplate 方法执行内容,这些方法允许您传入 PreparedStatementCreator。 您始终可以使用它来拦截调用(也许使用代理),这会导致取消在单独的线程上发生,由某些cond变为<代码> true。

public Results respondToUseRequest(Request req) {
    final AtomicBoolean cond = new AtomicBoolean(false);
    requestRegister.put(req, cond);
    return jdbcTemplate.query(new PreparedStatementCreator() {
             public PreparedStatement createPreparedStatement(Connection conn) {
               PreparedStatement stmt = conn.prepareStatement();
               return proxyPreparedStatement(stmt, cond);
             }
         }, 
         new ResultSetExtractor() { ... });
}        

这个canceller本身可以在成功完成后被取消; 例如

private final static ScheduledExecutorService scheduler =
                 Executors.newSingleThreadedScheduledExecutor();  

PreparedStatement proxyPreparedStatement(final PreparedStatement s, AtomicBoolean cond) {
    //InvocationHandler delegates invocations to the underlying statement
    //but intercepts a query 
    InvocationHandler h = new InvocationHandler() {

        public Object invoke(Object proxy, Method m, Object[] args) {
            if (m.getName().equals("executeQuery") {
                Runnable cancel = new Runnable() {
                    public void run() { 
                        try {
                            synchronized (cond) {
                                while (!cond.get()) cond.wait();
                                s.cancel(); 
                            }
                        } catch (InterruptedException e) { }
                    } 
                }
                Future<?> f = scheduler.submit(cancel);
                try {
                    return m.invoke(s, args);
                } finally {
                    //cancel the canceller upon succesful completion
                    if (!f.isDone()) f.cancel(true); //will cause interrupt
                }
            }
            else {
                return m.invoke(s, args);
            }   
        }

    }

    return (PreparedStatement) Proxy.newProxyInstance(
                getClass().getClassLoader(), 
                new Class[]{PreparedStatement.class}, 
                h);

,现在响应用户取消的代码将如下所示:

cond.set(true);
synchronized (cond) { cond.notifyAll(); }

You can execute stuff via JdbcTemplate methods which allow you to pass in a PreparedStatementCreator. You could always use this to intercept invocations (perhaps using a Proxy) which caused a cancel to happen on a separate thread by some cond became true.

public Results respondToUseRequest(Request req) {
    final AtomicBoolean cond = new AtomicBoolean(false);
    requestRegister.put(req, cond);
    return jdbcTemplate.query(new PreparedStatementCreator() {
             public PreparedStatement createPreparedStatement(Connection conn) {
               PreparedStatement stmt = conn.prepareStatement();
               return proxyPreparedStatement(stmt, cond);
             }
         }, 
         new ResultSetExtractor() { ... });
}        

This canceller could itself be cancelled upon successful completion; for example

private final static ScheduledExecutorService scheduler =
                 Executors.newSingleThreadedScheduledExecutor();  

PreparedStatement proxyPreparedStatement(final PreparedStatement s, AtomicBoolean cond) {
    //InvocationHandler delegates invocations to the underlying statement
    //but intercepts a query 
    InvocationHandler h = new InvocationHandler() {

        public Object invoke(Object proxy, Method m, Object[] args) {
            if (m.getName().equals("executeQuery") {
                Runnable cancel = new Runnable() {
                    public void run() { 
                        try {
                            synchronized (cond) {
                                while (!cond.get()) cond.wait();
                                s.cancel(); 
                            }
                        } catch (InterruptedException e) { }
                    } 
                }
                Future<?> f = scheduler.submit(cancel);
                try {
                    return m.invoke(s, args);
                } finally {
                    //cancel the canceller upon succesful completion
                    if (!f.isDone()) f.cancel(true); //will cause interrupt
                }
            }
            else {
                return m.invoke(s, args);
            }   
        }

    }

    return (PreparedStatement) Proxy.newProxyInstance(
                getClass().getClassLoader(), 
                new Class[]{PreparedStatement.class}, 
                h);

So now the code that is responding to a user's cancellation would look like:

cond.set(true);
synchronized (cond) { cond.notifyAll(); }
寂寞笑我太脆弱 2024-08-01 20:09:27

您可以在 JdbcTemplate 上注册一个 StatementCallback 类型的回调对象,该对象将以当前活动语句作为参数执行。 在此回调中,您可以取消该语句:

simpleJdbcTemplate.getJdbcOperations().execute(new StatementCallback() {

    @Override
    public Object doInStatement(final Statement statement) throws SQLException, DataAccessException {
        if (!statement.isClosed()) {
            statement.cancel();
        }

        return null;
    }
});

You can register a callback object of type StatementCallback on JdbcTemplate that will get executed with the currently active statement as a parameter. In this callback you can then cancel the statement:

simpleJdbcTemplate.getJdbcOperations().execute(new StatementCallback() {

    @Override
    public Object doInStatement(final Statement statement) throws SQLException, DataAccessException {
        if (!statement.isClosed()) {
            statement.cancel();
        }

        return null;
    }
});
烟花肆意 2024-08-01 20:09:27

我假设Spring你指的是使用JdbcDaoTemplate和/或JdbcTemplate? 如果是这样,这并不能真正帮助或阻碍您解决问题。

我假设您的用例是您正在一个线程中执行 DAO 操作,而另一个线程进入并想要取消第一个线程的操作。

你要解决的第一个问题是,第二个线程如何知道要取消哪个线程? 这是一个具有固定数量线程的 GUI,还是具有多个线程的服务器?

一旦解决了该部分,您需要弄清楚如何取消第一个线程中的语句。 一种简单的方法是将第一个线程的PreparedStatement存储在某个字段中(可能在一个简单的字段中,可能在线程ID到语句的映射中),允许第二个线程进入,检索statwment并调用cancel()在上面。

请记住,cancel() 可能会阻塞,具体取决于您的 JDBC 驱动程序和数据库。 另外,请确保您认真考虑这里的同步,您的线程是否会陷入战斗。

I assume by Spring you mean the use of JdbcDaoTemplate and/or JdbcTemplate? If so, this doesn't really help or hinder you in solving your problem.

I'll assume your use case is that you're executing a DAO operation in one thread, and another thread comes in and wants to cancel the first thread's operation.

The first problem you have to solve is, how does the second thread know which one to cancel? Is this a GUI with a fixed number of threads, or a server with several?

Once you've solved that part, you need to figure out how to cancel the statement in the first thread. One simple approach to this would be to store the first thread's PreparedStatement in a field somewhere (perhaps in a simple field, perhaps in a map of thread ID to statements), allowing the second thread to come in, retrieve the statwmentand call cancel() on it.

Bear in mind that it's possible that cancel() may just block, depending on your JDBC driver and database. Also, make sure you think hard about synchronization here, are your threads are going to get into a fight.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文