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

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

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

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

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

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


person Steve McLeod    schedule 28.06.2009    source источник


Ответы (4)


Позвольте мне упростить ответ 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 некоторому другому потоку перед фактическим выполнением запроса или просто сохранить его как переменную-член. В противном случае вы действительно ничего не сможете отменить ...

person itsadok    schedule 29.07.2009

Вы можете выполнять что-то с помощью 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(); }
person oxbow_lakes    schedule 28.06.2009
comment
Интересный и странный пример, я не совсем уверен, как это можно применить к рассматриваемой проблеме. Очевидно, что 10-секундную автоматическую отмену нужно будет заменить на что-то срабатывающее извне. - person skaffman; 29.06.2009
comment
Почему он должен запускаться извне? В OP ничего не сказано о том, что это, скажем, определяемое пользователем - person oxbow_lakes; 29.06.2009
comment
OP здесь: на самом деле, я хочу, чтобы отмена запускалась в ответ на действие пользователя. - person Steve McLeod; 29.06.2009
comment
Тогда это тоже довольно просто с моим методом. Вместо действия отмены, которое просто засыпает, вы можете запустить какой-то слушатель, который будит отменяющее действие по завершении. Я изменил свой ответ - person oxbow_lakes; 29.06.2009
comment
Модификации, которые я сделал, являются всего лишь доказательством правильности концепции; очевидно, вы не хотели бы заставлять потоки пула потоков ждать в реальности - вы бы добавили какой-то UserTriggerListener - person oxbow_lakes; 29.06.2009
comment
Кажется, этот код предполагает, что у меня уже есть ссылка на это утверждение. У меня проблема не в том, как отменить, если у меня есть ссылка на утверждение. Проблема в том, как получить заявление. Вот чего я не могу понять. - person Steve McLeod; 30.06.2009
comment
Но я сказал это в первой строке своего ответа: используйте механизм, который Spring предоставляет вам, чтобы использовать PreparedStatementCreator. Вам будет предоставлено соединение, и вы захотите создать этот прокси-сервер (см. Выше), делегирующий PS, который вы создаете самостоятельно из параметра Connection. - person oxbow_lakes; 30.06.2009
comment
Конечно, вам нужно будет где-то зарегистрировать переменную cond с запросом пользователя, чтобы вы могли позже разбудить ожидающую отмену - person oxbow_lakes; 30.06.2009
comment
Я снова изменил свой ответ - person oxbow_lakes; 01.07.2009

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

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

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

        return null;
    }
});
person Jonas    schedule 26.11.2009

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

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

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

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

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

person skaffman    schedule 29.06.2009
comment
Это отличная и подробная информация, но я хочу получить ответ на следующий вопрос: как получить ссылку на оператор при выполнении запроса через JdbcTemplate? - person Steve McLeod; 29.06.2009
comment
Что ж, вы должны были так сказать :) Как сказал @oxbow, вы можете использовать настраиваемый экземпляр PreparedStatementCreator, чтобы получить контроль над созданием оператора, а затем передать его в JdbcTemplate. - person skaffman; 29.06.2009