2009-06-28 11 views
11

La classe JDBC java.sql.Statement ha un metodo cancel(). Questo può essere chiamato in un'altra discussione per cancellare un'istruzione correntemente in esecuzione.Come posso cancellare una query di lunga durata usando Spring e JDBCTemplate?

Come posso ottenere questo utilizzando Spring? Non riesco a trovare un modo per ottenere un riferimento a un'istruzione quando si esegue una query. Né posso trovare un metodo simile alla cancellazione.

Ecco alcuni esempi di codice. Immaginate questo richiede fino a 10 secondi per l'esecuzione, e, talvolta, su richiesta dell'utente, voglio annullarla:

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

Come dovrei modificare questo quindi ho un riferimento a un oggetto java.sql.Statement?

risposta

11

Vorrei semplificare la risposta di oxbow_lakes: è possibile utilizzare la variante PreparedStatementCreator del metodo di query per ottenere l'accesso all'istruzione.

Quindi il codice:

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

dovrebbe girare in:

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

Ora per annullare si può chiamare

stmt[0].cancel() 

Probabilmente si desidera dare un riferimento a stmt a qualche altro thread prima di eseguire effettivamente la query, o semplicemente memorizzarlo come membro variabile e. In caso contrario, non è possibile annullare nulla ...

1

È possibile eseguire materiale tramite i metodi JdbcTemplate che consentono di passare in un PreparedStatementCreator. Si può sempre usare questo per intercettare le invocazioni (magari usando uno Proxy) che ha causato un cancel che si verifica su un thread separato da qualche cond diventato 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() { ... }); 
}   

Questo canceller potrebbe essere annullato in caso di completamento con esito positivo; per esempio

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

Così ora il codice che risponde a cancellazione di un utente sarebbe simile:

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

Un esempio interessante e strano, non del tutto sicuro di come possa essere applicato al problema in questione. Ovviamente l'annullamento automatico di 10 secondi dovrebbe essere sostituito con qualcosa attivato esternamente. – skaffman

+0

Perché dovrebbe essere attivato esternamente? L'OP non ha menzionato nulla riguardo ad esso, ad esempio, l'OP –

+0

definito dall'utente: in realtà, desidero che l'annullamento venga attivato in risposta all'azione dell'utente. –

0

Presumo entro la primavera si intende l'uso di JdbcDaoTemplate e/o JdbcTemplate? Se è così, questo non aiuta o ostacola veramente nel risolvere il tuo problema.

Suppongo che il tuo caso di utilizzo sia che stai eseguendo un'operazione DAO in un thread e che un altro thread arrivi e desideri annullare l'operazione del primo thread.

Il primo problema da risolvere è, in che modo il secondo thread sa quale annullare? Si tratta di una GUI con un numero fisso di thread o un server con diversi?

Una volta risolta la parte, è necessario capire come annullare l'istruzione nel primo thread. Un approccio semplice a questo sarebbe archiviare il PreparedStatement del primo thread in un campo da qualche parte (magari in un campo semplice, magari in una mappa di ID thread in istruzioni), consentendo al secondo thread di entrare, recuperare lo statwment e chiamare cancel() su di essa.

Tenere presente che è possibile che cancel() blocchi solo, a seconda del driver JDBC e del database. Inoltre, assicurati di pensare seriamente alla sincronizzazione qui, i tuoi thread si metteranno a litigare.

+0

Questa è una grande e dettagliata informazione, ma la domanda che cerco di essere risposta è questa: come posso ottenere un riferimento a una dichiarazione quando si esegue una query tramite JdbcTemplate? –

+0

Beh, avresti dovuto dirlo :) Come ha detto @oxbow, puoi usare un'istanza personalizzata di PreparedStatementCreator per ottenere il controllo sulla creazione delle istruzioni e quindi passarla a JdbcTemplate. – skaffman

0

È possibile registrare un oggetto di richiamata di tipo StatementCallback su JdbcTemplate che verrà eseguito con l'istruzione attualmente attiva come parametro. In questa richiamata è possibile annullare la dichiarazione:

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

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

     return null; 
    } 
}); 
Problemi correlati