Как отменить длительный запрос с помощью Spring и JDBCTemplate?

Класс JDBC java.sql.Statement имеет метод cancel(). Это можно вызвать в другом потоке, чтобы отменить текущий выполняемый оператор.

Как я могу достичь этого, используя Spring? Я не могу найти способ получить ссылку на инструкцию при запуске запроса. Я также не могу найти метод отмены.

Вот пример кода. Представьте, что для выполнения требуется до 10 секунд, а иногда и по запросу пользователя, я хочу отменить его:

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

Как мне изменить это, чтобы у меня была ссылка на объект java.sql.Statement?

Ответ 1

Позвольте мне упростить ответ 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

Вы можете выполнить материал с помощью методов 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

Я предполагаю, что Spring означает использование JdbcDaoTemplate и/или JdbcTemplate? Если это так, это не поможет или не поможет вам решить вашу проблему.

Я предполагаю, что ваш случай использования заключается в том, что вы выполняете операцию DAO в одном потоке, а другой поток приходит и хочет отменить операцию первого потока.

Первой проблемой, которую вы должны решить, является то, как второй поток знает, какой из них отменять? Является ли это графическим интерфейсом с фиксированным числом потоков или сервером с несколькими?

Как только вы решите эту часть, вам нужно выяснить, как отменить оператор в первом потоке. Один простой подход к этому заключается в том, чтобы сохранить первый поток PreparedStatement в каком-либо поле (возможно, в простом поле, возможно, на карте идентификатора потока для операторов), позволяя второму потоку войти, получить statwmentand call cancel() в теме.

Имейте в виду, что возможно, что cancel() может просто блокироваться, в зависимости от вашего драйвера JDBC и базы данных. Кроме того, убедитесь, что вы серьезно думаете о синхронизации здесь, ваши потоки собираются вступить в бой.

Ответ 4

Вы можете зарегистрировать объект обратного вызова типа StatementCallback на JdbcTemplate, который будет выполнен с текущим активным оператором в качестве параметра. В этом обратном вызове вы можете отменить утверждение:

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

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

        return null;
    }
});