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

时间:2009-06-28 10:06:36

标签: java spring jdbctemplate

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

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

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

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

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

4 个答案:

答案 0 :(得分:12)

让我简化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,或者只是将其存储为成员变量。否则,你真的无法取消任何东西......

答案 1 :(得分:1)

您可以通过JdbcTemplate方法执行内容,以便传入PreparedStatementCreator。您总是可以使用它来拦截调用(可能使用Proxy),这导致cancel在某个单独的线程上发生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(); }

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

{{1}}

答案 2 :(得分:0)

我认为Spring是指使用JdbcDaoTemplate和/或JdbcTemplate?如果是这样,这并没有真正帮助或妨碍您解决问题。

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

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

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

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

答案 3 :(得分:0)

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

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

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

        return null;
    }
});