代码之家  ›  专栏  ›  技术社区  ›  Steve McLeod

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

  •  12
  • Steve McLeod  · 技术社区  · 15 年前

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

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

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

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

    我该如何修改这个以便引用 java.sql.statement语句 对象?

    4 回复  |  直到 15 年前
        1
  •  11
  •   marc_s    8 年前

    让我简化一下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 在实际运行查询之前,或者将其存储为成员变量。否则,你不能取消任何东西…

        2
  •  1
  •   oxbow_lakes    15 年前

    你可以通过 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(); }
    
        3
  •  0
  •   skaffman    15 年前

    我认为在Spring中,您的意思是使用JDBCDAoTemplate和/或JDBCTemplate?如果是这样,这并不能真正帮助或阻碍你解决问题。

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

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

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

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

        4
  •  0
  •   Jonas    14 年前

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

    simpleJdbcTemplate.getJdbcOperations().execute(new StatementCallback() {
    
        @Override
        public Object doInStatement(final Statement statement) throws SQLException, DataAccessException {
            if (!statement.isClosed()) {
                statement.cancel();
            }
    
            return null;
        }
    });