Как я могу отменить длительный запрос, используя Spring и JDBCTemplate? - PullRequest
13 голосов
/ 28 июня 2009

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

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

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

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

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

Ответы [ 4 ]

12 голосов
/ 29 июля 2009

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

1 голос
/ 28 июня 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(); }
0 голосов
/ 26 ноября 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;
    }
});
0 голосов
/ 29 июня 2009

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

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

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

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

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

...