Как отменить длительный запрос с помощью 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;
}
});