web-dev-qa-db-ja.com

Hibernate hql、同じクエリで複数の更新ステートメントを実行

Hibernate Hqlの同じクエリで複数の更新ステートメントを実行したい。以下のように:

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

同じexecuteUpdate呼び出しで、Table1のレコードを更新し、Table2からレコードを削除します。

それは可能ですか?

10
Nebras

つまり、探しているのはJDBCでのバッチ処理のようなものです。これは、Hibernateが一括更新クエリ用に提供するものではなく、Hibernateで考慮されるかどうかは疑問です。

私の過去の経験から、HQLのバッチ処理機能は実際にはほとんど役に立ちません。何かがSQL + JDBCでは有用であるがHQLでは有用ではないというのは奇妙に聞こえるかもしれません。説明しようとします。

通常、Hibernate(または他の同様のORM)で作業する場合、エンティティに対して作業します。 Hibernateは、エンティティの状態をDBと同期する責任を負います。これは、JDBCバッチ処理がパフォーマンスの向上に役立つ場合のほとんどです。ただし、Hibernateでは、一括更新クエリによって個々のエンティティの状態を変更しません。

例を擬似コードで示します。

JDBCでは、次のようなことを行うことができます(例で示したものを模倣しようとしています)。

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

JDBCロジックからHibernateへの単純な変換により、次のような結果が得られる場合があります。

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

同様のことをしているため、バッチ処理機能が必要だと思われます(例に基づいて、個々のレコードに対して一括更新を使用します)。しかし、それは[〜#〜] not [〜#〜] Hibernate/JPAでどのように行うべきか

(実際には、リポジトリを介して永続層アクセスをラップする方が良いです。ここでは、単に図を単純化しています)

_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
_

これにより、Hibernateは、変更/削除/挿入されたエンティティを検出し、JDBCバッチを使用してflush()でDB CUD操作を実行するのに十分なインテリジェントです。さらに重要なのは、これがORMの全体的な目的です。動作の豊富なエンティティを提供し、エンティティの内部状態の変更を永続的なストレージに「透過的に」反映できるようにすることです。

HQL一括更新は、DBの1つの一括更新のような他の使用法を目的としており、多くのレコードに影響を与えます。例:

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

このような種類の使用シナリオで多くのクエリを実行する必要はほとんどないため、DBラウンドトリップのオーバーヘッドはわずかであるため、一括更新クエリの利点とバッチ処理サポートはあまり重要ではありません。

意味のあるエンティティの動作によって実行するのが適切ではない更新クエリを大量に発行する必要がある場合があることを省略できません。このような場合、Hibernateが適切なツールであるかどうかを再検討する必要があります。クエリの発行方法を制御できるように、このようなユースケースで純粋なJDBCを使用することを検討できます。

3
Adrian Shum

同じexecuteUpdate呼び出しで、Table1のレコードを更新し、Table2からレコードを削除します。

それは可能ですか?

executeUpdate()は、単一の更新クエリを実行します。だから、できません。更新/削除するには、テーブルと同じ数の更新クエリを実行する必要があります。それに、クエリを分離するとコードがきれいになります:

  • クエリは読みやすく、パラメータ設定は読みやすく、エラーが発生しやすいものではありません。
  • クエリの実行をデバッグするには、クエリが独自のexecuteUpdate()で1つずつ処理されるかどうかを理解しやすくなります。

クエリを1つずつ送信する必要があるという意味ではありません。
バッチ処理は、複数のクエリを実行する場合のパフォーマンスを向上させるためにHibernateが提供する機能です。この機能を使用するには、機能を有効にする必要があります。 hibernate.jdbc.batch_sizeプロパティには適切な値を設定する必要があります。

バッチ処理を行っている場合、JDBCバッチ処理の使用を有効にする必要があります。これは、最適なパフォーマンスを実現するために不可欠です。 JDBCバッチサイズを適切な数(10〜50など)に設定します。

hibernate.jdbc.batch_size 20 ID識別子ジェネレーターを使用する場合、HibernateはJDBCレベルでの挿入バッチ処理を透過的に無効にします。

公式ドキュメント の他に:

Hibernateは、ID識別子ジェネレーターを使用する場合、JDBCレベルでのバッチ挿入を透過的に無効にします。

それでも、あなたの場合、Dragan Bozanovicがあなたのクエリで異なるテーブルを更新/削除すると説明したので、それは役に立たないでしょう。したがって、クエリされたテーブルと同じ数のバッチ実行を作成します。
したがって、各クエリを個別に実行する必要があります。トランザクションをコミットする必要があると判断した場合は、コミットします。

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

いいえ、できません。HibernateはこれにPreparedStatementsを使用するため(バインド変数のために適切です)、PreparedStatementsは複数の異なるステートメントで構成されるバッチをサポートしません。

PreparedStatementは、1つのステートメントのバインド変数の異なる組み合わせのみをバッチ処理できます。Hibernateは、永続コンテキスト(セッション)の変更をフラッシュするときに batch inserts/updates に使用します。

3

JPA一括更新/削除、つまりjavax.persistence.Query.executeUpdate()の呼び出しによって生成されたSQLは、JDBCに渡されるときにHibernateによってバッチ処理できません。 @DraganBozanovicと@AdrianShumはすでにこれを説明していますが、コメントに追加するために:executeUpdate()はint(更新または削除されたエンティティの数)を返します-Hibernateセッションのフラッシュに関係なく、データベースを呼び出さずにintを返す方法すぐに同期しますか? JPQL/HQL/SQLをクライアント側で評価する必要があります。これは、一括更新/削除されるエンティティがHibernateセッションに読み込まれなかった可能性があるため不可能です。さらに、更新/削除が実行されなかった場合データベース上すぐに、JPAエンティティを読み込む後続のクエリが古いデータを取得する可能性がありました。例:

  1. executeUpdateを使用して、ID> 1000のすべての顧客を一括削除します。
  2. iD = 1001の顧客エンティティを読み取ります。

1のexecuteUpdateが2の読み取り後まで延期できる場合、間違った答えが得られます(顧客はまだ存在します)。

JPAを使用してエンティティを読み取って更新し、Hibernateに更新SQL(バッチ処理可能)を生成させるか、JDBCを直接呼び出してバッチ更新を実行する必要があります。

1
coder

トランザクションメソッドで2つのクエリを個別に実行しないのはなぜですか

@Transactionalでメソッドに注釈を付けることにより、クエリのいずれかが失敗した場合、もう一方は実行されません。

 @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