¿Cómo puedo cancelar una consulta de larga duración usando Spring y JDBCTemplate?

StackOverflow https://stackoverflow.com/questions/1054642

  •  20-08-2019
  •  | 
  •  

Pregunta

La clase JDBC java.sql.Statement tiene un método cancel(). Esto se puede llamar en otro hilo para cancelar una declaración actualmente en ejecución.

¿Cómo puedo lograr esto usando Spring? No puedo encontrar una manera de obtener una referencia a una declaración cuando ejecuto una consulta. Tampoco puedo encontrar un método similar a cancelar.

Aquí hay un código de muestra. Imagine que esto demora hasta 10 segundos en ejecutarse y, a veces, a solicitud del usuario, quiero cancelarlo:

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

¿Cómo modificaría esto para tener una referencia a un objeto <=>?

¿Fue útil?

Solución

Permítanme simplificar la respuesta de oxbow_lakes: puede usar la variante PreparedStatementCreator del método de consulta para obtener acceso a la declaración.

Entonces su código:

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

Debería convertirse en:

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

Ahora para cancelar puedes llamar

stmt[0].cancel()

Probablemente desee dar una referencia a stmt a algún otro hilo antes de ejecutar la consulta, o simplemente almacenarlo como una variable miembro. De lo contrario, realmente no puede cancelar nada ...

Otros consejos

Puede ejecutar cosas mediante JdbcTemplate métodos que le permiten pasar un PreparedStatementCreator. Siempre puede usar esto para interceptar invocaciones (quizás usando un Proxy) que causó que cancel ocurriera en un hilo separado por algunos cond se convirtieron en 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() { ... });
}        

Este canceller podría cancelarse una vez completado con éxito; por ejemplo

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

Entonces, el código que responde a la cancelación de un usuario se vería así:

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

Supongo que por Spring te refieres al uso de JdbcDaoTemplate y / o JdbcTemplate? Si es así, esto realmente no te ayuda ni te impide resolver tu problema.

Asumiré que su caso de uso es que está ejecutando una operación DAO en un hilo, y otro hilo entra y quiere cancelar la operación del primer hilo.

El primer problema que tiene que resolver es, ¿cómo sabe el segundo hilo cuál cancelar? ¿Es esta una GUI con un número fijo de subprocesos o un servidor con varios?

Una vez que haya resuelto esa parte, necesita descubrir cómo cancelar la declaración en el primer hilo. Un enfoque simple para esto sería almacenar la Declaración Preparada del primer subproceso en un campo en algún lugar (quizás en un campo simple, quizás en un mapa de ID de subproceso a las declaraciones), permitiendo que el segundo subproceso entre, recupere el estado y llame a cancelar () en eso.

Tenga en cuenta que es posible que cancel () simplemente se bloquee, dependiendo de su controlador JDBC y base de datos. Además, asegúrese de pensar mucho acerca de la sincronización aquí, si sus hilos van a entrar en una pelea.

Puede registrar un objeto de devolución de llamada de tipo StatementCallback en JdbcTemplate que se ejecutará con la instrucción actualmente activa como parámetro. En esta devolución de llamada, puede cancelar la declaración:

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

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

        return null;
    }
});
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top