web-dev-qa-db-fra.com

Hibernate hql, exécute plusieurs instructions de mise à jour dans la même requête

Je veux exécuter plusieurs instructions de mise à jour dans la même requête dans Hibernate Hql. comme ci-dessous:

hql = " update Table1 set prob1=null where id=:id1; "
                + " delete from Table2 where id =:id2 ";
...
query.executeUpdate();

dans le même appel executeUpdate, je souhaite mettre à jour les enregistrements de Table1 et supprimer les enregistrements de Table2.

Est-ce possible?

10
Nebras

En bref, ce que vous recherchez est quelque chose comme le traitement par lots dans JDBC. Cela n'est pas fourni par Hibernate pour la requête de mise à jour en masse, et je doute qu'il soit jamais pris en compte pour Hibernate.

D'après mon expérience passée, la fonction de traitement par lots pour HQL est rarement utile dans la vie réelle. Il peut sembler étrange que quelque chose soit utile dans SQL + JDBC mais pas dans HQL. Je vais essayer d'expliquer.

Habituellement, lorsque nous travaillons avec Hibernate (ou un autre ORM similaire), nous travaillons contre des entités. Hibernate sera responsable de synchroniser l'état de nos entités avec DB, ce qui est la plupart des cas où le traitement par lots JDBC peut aider à améliorer les performances. Cependant, dans Hibernate, nous ne modifions pas l'état de chaque entité par une requête de mise à jour en masse.

Donnez simplement un exemple, en pseudo-code:

Dans JDBC, vous pouvez faire quelque chose comme (j'essaie d'imiter ce que vous montrez dans votre exemple):

List<Order> orders = findOrderByUserId(userName);
for (Order order: orders) {
    if (order outstanding quantity is 0) {
        dbConn.addBatch("update ORDER set STATE='C' where ID=:id", order.id);
    } else if (order is after expriation time) {
        dbConn.addBatch("delete ORDER where ID=:id", order.id);
    }
}
dbConn.executeBatch();

La traduction naïve de la logique JDBC vers Hibernate peut vous donner quelque chose comme ceci:

List<Order> orders = findOrderByUserId(userName);
for (Order order: orders) {
    if (order outstanding quantity is 0) {
        q = session.createQuery("update Order set state='C' where id=:id");
        q.setParameter("id", order.id);
        q.executeUpdate();
    } else if (order is after expriation time) {
        q = session.createQuery("delete Order where id=:id");
        q.setParameter("id", order.id);
        q.executeUpdate();
    }
}

Je pense que vous pensez que vous avez besoin de la fonction de traitement par lots parce que vous faites quelque chose de similaire (basé sur votre exemple, que vous utilisez la mise à jour en bloc pour un enregistrement individuel). Cependant, c'est PAS comment les choses devraient être faites dans Hibernate/JPA

(En fait, il est préférable d'envelopper l'accès à la couche de persistance via un référentiel, ici je simplifie simplement l'image)

List<Order> orders = findOrderByUserId(userName);
for (Order order: orders) {
    if (order.anyOutstanding()) {
        order.complete();    // which internally update the state
    } else if (order.expired) {
        session.delete(order);
    }
}

session.flush();   // or you may simply leave it to flush automatically before txn commit

Ce faisant, Hibernate est suffisamment intelligent pour détecter les entités modifiées/supprimées/insérées et utiliser le lot JDBC pour effectuer les opérations DB CUD dans flush(). Plus important encore, c'est tout le but d'ORM: nous voulons fournir des entités riches en comportement avec lesquelles travailler, pour lesquelles le changement d'état interne des entités peut être reflété de manière "transparente" dans le stockage persistant.

La mise à jour en masse HQL vise une autre utilisation, qui ressemble à une mise à jour en masse de la base de données pour affecter un grand nombre d'enregistrements, par exemple:

q = session.createQuery("update Order set state='C' " 
                        + " where user.id=:user_id "
                        + " and outstandingQty = 0 and state != 'C' ");
q.setParameter("user_id", userId);
q.executeUpdate();

Il est rarement nécessaire d'exécuter un grand nombre de requêtes dans ce type de scénario d'utilisation.Par conséquent, la surcharge de l'aller-retour DB est insignifiante et, par conséquent, les avantages et la prise en charge du traitement par lots pour les requêtes de mise à jour en masse sont rarement significatifs.

Je ne peux pas omettre qu'il existe des cas où vous avez vraiment besoin d'émettre un grand nombre de requêtes de mise à jour qui ne sont pas appropriées pour être effectuées par un comportement d'entité significatif. Dans ce cas, vous voudrez peut-être reconsidérer si Hibernate est le bon outil à utiliser. Vous pouvez envisager d'utiliser JDBC pur dans un tel cas d'utilisation afin de contrôler la façon dont les requêtes sont émises.

3
Adrian Shum

dans le même appel executeUpdate, je souhaite mettre à jour les enregistrements de Table1 et supprimer les enregistrements de Table2.

Est-ce possible?

executeUpdate() exécute une seule requête de mise à jour. Donc, non, vous ne pouvez pas le faire. Vous devez exécuter autant de requêtes de mise à jour que de tables à mettre à jour/supprimer. En outre, il rend le code plus propre si vous séparez les requêtes:

  • les requêtes sont plus faciles à lire et le réglage des paramètres est lisible et non sujet aux erreurs.
  • pour déboguer l'exécution des requêtes, il serait plus facile de comprendre si les requêtes sont gérées une par une dans leur propre executeUpdate()

Cela ne signifie pas que les requêtes doivent obligatoirement être transmises une par une.
Le traitement par lots est une fonctionnalité fournie par Hibernate pour améliorer les performances lorsque vous souhaitez exécuter plusieurs requêtes. Vous devez activer la fonctionnalité pour l'utiliser. hibernate.jdbc.batch_size La propriété doit être définie avec une valeur appropriée.

Si vous effectuez un traitement par lots, vous devrez activer l'utilisation du traitement par lots JDBC. Ceci est absolument essentiel si vous souhaitez obtenir des performances optimales. Définissez la taille du lot JDBC sur un nombre raisonnable (10-50, par exemple):

hibernate.jdbc.batch_size 20 Hibernate désactive le batch d'insertion au niveau JDBC de manière transparente si vous utilisez un générateur d'identifiant.

En plus de documentation officielle :

Hibernate désactive le batch d'insertion au niveau JDBC de manière transparente si vous utilisez un générateur d'identifiant.

Néanmoins, dans votre cas, cela sera inutile car comme Dragan Bozanovic vous l'a expliqué, vous mettez à jour/supprimez différentes tables dans vos requêtes. Ainsi, cela créerait autant d'exécutions par lots que de tables interrogées.
Vous devez donc exécuter chaque requête individuellement. Il suffit de valider () la transaction lorsque vous estimez qu'elle devrait être:

hql = "update Table1 set prob1=null where id=:id1;"
...
query.setParameter("id1",...);
query.executeUpdate();
hql = "delete from Table2 where id =:id2";
...
query.executeUpdate();
query.setParameter("id2",...);
..
tx.commit();
4
davidxxx

Non, ce n'est pas possible, car Hibernate utilise PreparedStatements pour cela (ce qui est bon en raison des variables de liaison) et PreparedStatements ne prend pas en charge les lots composés de plusieurs instructions différentes.

PreparedStatement ne peut traiter par lots différentes combinaisons de variables de liaison pour une instruction, qu'Hibernate utilise pour insertions/mises à jour par lots lors du vidage des modifications dans le contexte de persistance (session).

3
Dragan Bozanovic

Le SQL généré par les mises à jour/suppressions en masse JPA, c'est-à-dire les appels à javax.persistence.Query.executeUpdate () ne peut pas être mis en lot par Hibernate lorsqu'il est transmis à JDBC. @DraganBozanovic et @AdrianShum l'ont déjà expliqué, mais pour ajouter à leurs commentaires: executeUpdate () renvoie un entier (le nombre d'entités mises à jour ou supprimées) - indépendamment du vidage de la session Hibernate, comment pourrait-il être renvoyé sans appeler la base de données immédiatement et de manière synchrone? Le JPQL/HQL/SQL devrait être évalué côté client, ce qui n'est pas possible car les entités à mettre à jour/supprimer en masse peuvent même ne pas avoir été lues dans la session Hibernate. De plus, si la mise à jour/suppression n'a pas été exécutée sur la base de données immédiatement, les requêtes ultérieures à lire dans les entités JPA pourraient obtenir des données périmées. Exemple:

  1. executeUpdate pour supprimer en bloc tous les clients avec un ID> 1000.
  2. lire l'entité client avec l'ID = 1001.

Si le executeUpdate à 1 était autorisé à être différé jusqu'à la lecture à 2, alors vous obtenez la mauvaise réponse (le client existe toujours).

Vous devez soit lire les entités à l'aide de JPA, les mettre à jour et laisser Hibernate générer le SQL de mise à jour (qu'il peut traiter par lots), soit appeler directement JDBC pour effectuer des mises à jour par lots.

1
coder

Pourquoi ne pas exécuter les deux requêtes séparément dans une méthode transactionnelle

En annotant la méthode avec @Transactional, si l'une des requêtes échoue, l'autre ne s'exécute pas.

 @Transactional(propagation = Propagation.REQUIRED, readOnly = false)

 public void executeQuery(Obj1 obj1) {

 String query="update table1 set actualRepaymentAmount=expectedRepaymentAmount,active='Y'  where loanCaseId = '"+caseId+"'";
        sessionFactory.getCurrentSession().createQuery(query).executeUpdate();

 query="update table2 set loanStatus='C' where loanCaseId = '"+caseId+"'";  
    sessionFactory.getCurrentSession().createQuery(query).executeUpdate();

        ...

 }
0
Olakunle Awotunbo