2009-06-28 79 views
11

JDBC java.sql.Statement類有一個cancel()方法。這可以在另一個線程中調用來取消當前正在運行的語句。如何取消使用Spring和JDBCTemplate的長時間運行的查詢?

如何使用Spring實現這一點?我無法找到一種方法在運行查詢時獲取對語句的引用。我也找不到類似取消的方法。

下面是一些示例代碼。想象一下,這需要長達10秒的執行,有時用戶的要求,我想取消:

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

我將如何修改這個讓我有一個java.sql.Statement對象的引用?

回答

11

讓我簡化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

您可以通過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(); } 
+0

一個有趣而奇怪的例子,並不完全確定如何將其應用於手頭的問題。很明顯,10秒鐘的自動取消將需要用外部觸發的東西來取代。 – skaffman 2009-06-29 07:25:40

+0

爲什麼它必須被外部觸發? OP沒有提及任何關於它的事情,比如用戶定義的 – 2009-06-29 07:57:20

0

我的春天假設你的意思是使用JdbcDaoTemplate和/或JdbcTemplate的嗎?如果是這樣,這並不能真正幫助或阻礙你解決你的問題。

我假設你的用例是你在一個線程中執行一個DAO操作,而另一個線程進來並且想要取消第一個線程的操作。

您必須解決的第一個問題是,第二個線程如何知道要取消哪一個?這是一個具有固定數量的線程的圖形用戶界面,還是具有多個線程的服務器?

一旦你解決了這個部分,你需要弄清楚如何取消第一個線程中的語句。一個簡單的方法是將第一個線程的PreparedStatement存儲在某個字段中(可能在一個簡單的字段中,也許在線程ID到語句的地圖中),允許第二個線程進入,檢索statwment並調用cancel()在上面。

請記住,取決於您的JDBC驅動程序和數據庫,cancel()可能會被阻塞。另外,請確保你在這裏認真思考同步問題,你的線索是否會陷入戰鬥。

0

你可以在JdbcTemplate上註冊一個類型爲StatementCallback的回調對象,它將以當前活動語句作爲參數執行。在此回調中,您可以取消聲明:

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

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

     return null; 
    } 
});