Comment puis-je annuler une requête de longue durée en utilisant Spring et JDBCTemplate?

StackOverflow https://stackoverflow.com/questions/1054642

  •  20-08-2019
  •  | 
  •  

Question

La classe JDBC java.sql.Statement a une méthode cancel(). Ceci peut être appelé dans un autre thread pour annuler une instruction en cours d'exécution.

Comment puis-je réaliser cela avec Spring? Je ne trouve pas de moyen d'obtenir une référence à une instruction lors de l'exécution d'une requête. Je ne peux pas non plus trouver une méthode semblable à une annulation.

Voici un exemple de code. Imaginez que cela prenne jusqu'à 10 secondes pour s'exécuter, et parfois, à la demande de l'utilisateur, je souhaite l'annuler:

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

Comment pourrais-je modifier cela afin d'avoir une référence à un <=> objet?

Était-ce utile?

La solution

Permettez-moi de simplifier la réponse d'oxbow_lakes: vous pouvez utiliser la PreparedStatementCreator variante de la méthode de requête pour accéder à la déclaration.

Donc votre code:

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

Devrait devenir:

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);
    }
});

Maintenant, pour annuler, vous pouvez simplement appeler

stmt[0].cancel()

Vous voulez probablement donner une référence à stmt à un autre thread avant d'exécuter la requête, ou simplement le stocker en tant que variable membre. Sinon, vous ne pouvez pas vraiment annuler quoi que ce soit ...

Autres conseils

Vous pouvez exécuter des commandes via JdbcTemplate des méthodes vous permettant de passer un PreparedStatementCreator. Vous pouvez toujours l'utiliser pour intercepter des invocations (peut-être en utilisant un Proxy), ce qui a provoqué un cancel sur un thread séparé par un cond devenu 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() { ... });
}        

Ce canceller pourrait lui-même être annulé en cas de succès; par exemple

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);

Le code qui répond à l'annulation d'un utilisateur se présentera ainsi:

cond.set(true);
synchronized (cond) { cond.notifyAll(); }

Je suppose que vous entendez par Spring utiliser JdbcDaoTemplate et / ou JdbcTemplate? Si tel est le cas, cela ne vous aidera pas vraiment à résoudre votre problème.

Je suppose que votre cas d'utilisation est que vous exécutez une opération DAO dans un thread et qu'un autre thread entre et souhaite annuler l'opération du premier.

Le premier problème que vous devez résoudre est comment le second thread sait-il lequel annuler? S'agit-il d'une interface graphique avec un nombre fixe de threads ou d'un serveur avec plusieurs?

Une fois que vous avez résolu cette partie, vous devez trouver un moyen d’annuler la déclaration dans le premier fil de discussion. Une approche simple consiste à stocker le PreparedStatement du premier thread dans un champ quelque part (éventuellement dans un champ simple, peut-être dans une mappe d'ID de thread en instructions), en permettant au second thread d'entrer, de récupérer le statwment et l'appel cancel (). dessus.

N'oubliez pas qu'il est possible que cancel () bloque simplement, en fonction du pilote JDBC et de la base de données. Assurez-vous également que vous réfléchissez bien à la synchronisation, car vos discussions vont se disputer.

Vous pouvez enregistrer un objet de rappel de type StatementCallback sur JdbcTemplate à exécuter avec l'instruction actuellement active en tant que paramètre. Dans ce rappel, vous pouvez ensuite annuler la déclaration:

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

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

        return null;
    }
});
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top