Domanda

La classe JDBC java.sql.Statement ha un metodo cancel(). Questo può essere chiamato in un altro thread per annullare un'istruzione attualmente in esecuzione.

Come posso raggiungere questo obiettivo usando Spring? Non riesco a trovare un modo per ottenere un riferimento a un'istruzione durante l'esecuzione di una query. Né posso trovare un metodo simile a un annullamento.

Ecco un po 'di codice di esempio. Immagina che ciò richieda fino a 10 secondi per l'esecuzione e, a volte, su richiesta dell'utente, voglio annullarlo:

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

Come dovrei modificarlo in modo da avere un riferimento a un <=> oggetto?

È stato utile?

Soluzione

Consentimi di semplificare la risposta di oxbow_lakes: puoi utilizzare la variante PreparedStatementCreator del metodo di query per accedere all'istruzione.

Quindi il tuo codice:

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

Dovrebbe trasformarsi 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 puoi semplicemente chiamare

stmt[0].cancel()

Probabilmente vuoi dare un riferimento a stmt ad un altro thread prima di eseguire effettivamente la query, o semplicemente archiviarlo come variabile membro. Altrimenti, non puoi davvero cancellare nulla ...

Altri suggerimenti

Puoi eseguire cose tramite JdbcTemplate metodi che ti permettono di passare un PreparedStatementCreator. Puoi sempre usarlo per intercettare le invocazioni (forse usando un Proxy) che ha causato un cancel che si è verificato su un thread separato da alcuni 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 cancellato se completato correttamente; 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);

Quindi ora il codice che risponde alla cancellazione di un utente sarebbe simile a:

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

Presumo che per primavera intendi l'uso di JdbcDaoTemplate e / o JdbcTemplate? In tal caso, ciò non ti aiuta né ti ostacola nella risoluzione del problema.

Suppongo che il tuo caso d'uso sia che stai eseguendo un'operazione DAO in un thread, e un altro thread entra e vuole annullare l'operazione del primo thread.

Il primo problema che devi risolvere è, come fa il secondo thread a sapere quale annullare? È una GUI con un numero fisso di thread o un server con più?

Dopo aver risolto quella parte, devi capire come annullare l'istruzione nel primo thread. Un semplice approccio a questo sarebbe quello di memorizzare il PreparedStatement del primo thread in un campo da qualche parte (forse in un campo semplice, forse in una mappa dell'ID thread alle istruzioni), consentendo al secondo thread di entrare, recuperare lo statwment e chiamare cancel () su di esso.

Tieni presente che è possibile che cancel () possa bloccarsi, a seconda del driver e del database JDBC. Inoltre, assicurati di pensare seriamente alla sincronizzazione qui, se i tuoi thread entreranno in una rissa.

È possibile registrare un oggetto callback di tipo StatementCallback su JdbcTemplate che verrà eseguito con l'istruzione attualmente attiva come parametro. In questo callback è quindi 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;
    }
});
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top