Como posso cancelar uma consulta de longa duração usando Primavera e JDBCTemplate?
-
20-08-2019 - |
Pergunta
A classe java.sql.Statement
JDBC tem um método cancel()
. Isso pode ser chamado em outro segmento para cancelar uma instrução atualmente em execução.
Como posso conseguir isso usando Spring? Não consigo encontrar uma maneira de obter uma referência a uma declaração ao executar uma consulta. Também não posso encontrar uma cancelar-como método.
Aqui está um código de exemplo. Imagine isto leva até 10 segundos para executar, e às vezes a pedido do usuário, eu quero cancelá-lo:
final int i = simpleJdbcTemplate.queryForInt("select max(gameid) from game");
Como eu poderia modificar isso para que eu tenha uma referência a um objeto java.sql.Statement
?
Solução
Deixe-me simplificar a resposta das oxbow_lakes: você pode usar a variante PreparedStatementCreator
do método de consulta para ter acesso à instrução
Portanto, o seu código:
final int i = simpleJdbcTemplate.queryForInt("select max(gameid) from game");
Se transformar em:
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);
}
});
Agora, para cancelar você pode apenas chamar
stmt[0].cancel()
Você provavelmente vai querer dar uma referência para stmt
para algum outro fio antes de realmente executar a consulta, ou simplesmente armazená-lo como uma variável de membro. Caso contrário, você não pode realmente cancelar qualquer coisa ...
Outras dicas
Você pode executar coisas através de métodos JdbcTemplate
que permitem que você passar em um PreparedStatementCreator
. Você pode sempre usar isso para invocações de interceptação (talvez usando um Proxy
) que causou um cancel
acontecer em um segmento separado por alguns cond
tornou 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() { ... });
}
Esta canceller
pode-se ser cancelado após a conclusão; por exemplo
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);
Agora o código que está respondendo ao cancelamento de um usuário ficaria assim:
cond.set(true);
synchronized (cond) { cond.notifyAll(); }
Eu assumo pela Primavera você quer dizer o uso de JdbcDaoTemplate e / ou JdbcTemplate? Se assim for, isso realmente não ajuda ou atrapalha-lo a resolver o seu problema.
Eu vou assumir o seu caso de uso é que você está executando uma operação de DAO em um fio, e outro segmento chega e quer cancelar a operação do primeiro fio.
O primeiro problema que você tem para resolver é, como é que o segundo segmento saber qual cancelar? É este um GUI com um número fixo de threads, ou um servidor com vários?
Uma vez que você já resolveu essa parte, você precisa descobrir como cancelar a instrução no primeiro segmento. Uma abordagem simples para isso seria para armazenar PreparedStatement do primeiro segmento em um algum lugar campo (talvez em um campo simples, talvez em um mapa de ID thread para declarações), permitindo que o segundo segmento a entrar, para recuperar a chamada statwmentand cancelar () nele.
Tenha em mente que é possível que cancelar () pode apenas bloquear, dependendo do seu motorista e banco de dados JDBC. Além disso, certifique-se de pensar muito sobre sincronização aqui, são os seus tópicos estão indo para entrar em uma briga.
Você pode registrar um objeto callback do tipo StatementCallback
em JdbcTemplate
que serão executados com a declaração actualmente activa como um parâmetro. Neste callback então você pode cancelar a declaração:
simpleJdbcTemplate.getJdbcOperations().execute(new StatementCallback() {
@Override
public Object doInStatement(final Statement statement) throws SQLException, DataAccessException {
if (!statement.isClosed()) {
statement.cancel();
}
return null;
}
});