Hibernate Union の代替案
質問
Hibernate を使用してユニオン クエリを実装するには、どのような代替手段が必要ですか?現時点では Hibernate がユニオン クエリをサポートしていないことはわかっています。現時点でユニオンを作成する唯一の方法はビュー テーブルを使用することだと思います。
もう 1 つのオプションは、プレーンな jdbc を使用することですが、この方法では、サンプル/基準クエリのすべての利点と、テーブル/列に対して Hibernate が実行する Hibernate マッピング検証が失われます。
解決
VIEWを使用します。エンティティ名を使用して、同じクラスを異なるテーブル/ビューにマップできるため、重複はほとんどありません。そこにいても、それで問題ありません。
プレーンJDBCには別の隠れた問題があります:Hibernateセッションキャッシュを認識しないため、トランザクションの終わりまで何かがキャッシュされ、Hibernateセッションからフラッシュされない場合、JDBCクエリはそれを見つけられません。非常に不可解なこともあります。
他のヒント
id in (select id from ...) or id in (select id from ...)
e.g。 動作しない
の代わりにfrom Person p where p.name="Joe"
union
from Person p join p.children c where c.name="Joe"
できます
from Person p
where p.id in (select p1.id from Person p1 where p1.name="Joe")
or p.id in (select p2.id from Person p2 join p2.children c where c.name="Joe");
少なくともMySQLを使用すると、後でパフォーマンスの問題が発生します。代わりに、2つのクエリで貧乏人の結合を行う方が簡単な場合があります。
// use set for uniqueness
Set<Person> people = new HashSet<Person>((List<Person>) query1.list());
people.addAll((List<Person>) query2.list());
return new ArrayList<Person>(people);
多くの場合、1つの複雑なクエリよりも2つの単純なクエリの方が優れています。
編集:
例を示すために、サブセレクトソリューションからの結果のMySQLクエリのEXPLAIN出力を次に示します。
mysql> explain
select p.* from PERSON p
where p.id in (select p1.id from PERSON p1 where p1.name = "Joe")
or p.id in (select p2.id from PERSON p2
join CHILDREN c on p2.id = c.parent where c.name="Joe") \G
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: a
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 247554
Extra: Using where
*************************** 2. row ***************************
id: 3
select_type: DEPENDENT SUBQUERY
table: NULL
type: NULL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: NULL
Extra: Impossible WHERE noticed after reading const tables
*************************** 3. row ***************************
id: 2
select_type: DEPENDENT SUBQUERY
table: a1
type: unique_subquery
possible_keys: PRIMARY,name,sortname
key: PRIMARY
key_len: 4
ref: func
rows: 1
Extra: Using where
3 rows in set (0.00 sec)
最も重要なことは、1。行はインデックスを使用せず、20万行以上と見なします。悪い!両方のサブクエリがミリ秒単位である場合、このクエリの実行には0.7秒かかりました。
ウラジミールに同意しなければなりません。私もHQLでUNIONを使用することを検討しましたが、その方法を見つけることができませんでした。奇妙なことは、(Hibernate FAQで)UNIONがサポートされていないこと、UNIONに関連するバグレポートが「修正済み」とマークされていること、ステートメントがUNIONで切り捨てられると言っている人々のニュースグループ、およびそれが機能していることを報告している人々の他のニュースグループを見つけることができることでしたいい... 1日それをいじった後、HQLをプレーンSQLに移植することになりましたが、データベースのビューでそれを行うことは良い選択肢です。私の場合、クエリの一部は動的に生成されたため、代わりにコードでSQLを作成する必要がありました。
HQLでのユニオンを使用した1つの重要なシナリオ(多くの苦労をした)の解決策があります。
e.g。動作しない代わりに:-
select i , j from A a , (select i , j from B union select i , j from C) d where a.i = d.i
または
select i , j from A a JOIN (select i , j from B union select i , j from C) d on a.i = d.i
Hibernate HQLでできること-<!> gt;
Query q1 =session.createQuery(select i , j from A a JOIN B b on a.i = b.i)
List l1 = q1.list();
Query q2 = session.createQuery(select i , j from A a JOIN C b on a.i = b.i)
List l2 = q2.list();
その後、両方のリストを追加できます-<!> gt;
l1.addAll(l2);
ビューはより良いアプローチですが、hqlは通常ListまたはSet ...を返すため、list_1.addAll(list_2)を実行できます。ユニオンと比較して完全に悪いですが、動作するはずです。
おそらくもっと簡単な解決すべき問題がありました。私の「インスタンス」はJPAであり、JPAプロバイダーとしてHibernateを使用していました。
3つの選択(2つ目の場合は2つ)を複数の選択に分割し、返されたコレクションを結合して、「すべてを結合」を効果的に置き換えました。
私もこの苦痛を経験してきました-クエリが動的に生成される場合(例:Hibernate Criteria)、実用的な方法を見つけることができませんでした。
私にとって良いニュースは、Oracleデータベースで「または」を使用する場合、パフォーマンスの問題を解決するためにユニオンのみを調査していたことです。
Patrickが投稿した(セットを使用してプログラムで結果を結合する)ソリューションは、い(特に結果ページングも行いたいため)で十分でした。
パトリックが言ったように、各 SELECT から LIST を追加するのは良い考えですが、 UNION ALL のように機能することを覚えておいてください。この副作用を回避するには、オブジェクトが既に最終コレクションに追加されているかどうかを制御します。いいえの場合、追加します。
他に注意すべきことは、各 SELECT に JOIN がある場合、結果はオブジェクト配列のリストになります( リスト&lt; Objetc []&gt;
)を繰り返し、必要なオブジェクトのみを保持するように繰り返し処理する必要があります。
うまくいきますように。
ここでは特殊なケースを示しますが、独自の回避策を作成することをお勧めします。ここでの目標は、レコードが特定の基準を満たす 2 つの異なるテーブルからのレコードの総数をカウントすることです。この手法は、複数のテーブル/ソースからデータを集約する必要があるあらゆるケースで機能すると思います。
特別な中間クラスをいくつか設定しているので、名前付きクエリを呼び出すコードは短くて簡単ですが、名前付きクエリと組み合わせて通常使用するメソッドを使用してクエリを実行できます。
QueryParms parms=new QueryParms();
parms.put("PROCDATE",PROCDATE);
Long pixelAll = ((SourceCount)Fetch.row("PIXEL_ALL",parms,logger)).getCOUNT();
ここでわかるように、名前付きクエリは、union ステートメントに非常によく似ています。
@Entity
@NamedQueries({
@NamedQuery(
name ="PIXEL_ALL",
query = "" +
" SELECT new SourceCount(" +
" (select count(a) from PIXEL_LOG_CURR1 a " +
" where to_char(a.TIMESTAMP, 'YYYYMMDD') = :PROCDATE " +
" )," +
" (select count(b) from PIXEL_LOG_CURR2 b" +
" where to_char(b.TIMESTAMP, 'YYYYMMDD') = :PROCDATE " +
" )" +
") from Dual1" +
""
)
})
public class SourceCount {
@Id
private Long COUNT;
public SourceCount(Long COUNT1, Long COUNT2) {
this.COUNT = COUNT1+COUNT2;
}
public Long getCOUNT() {
return COUNT;
}
public void setCOUNT(Long COUNT) {
this.COUNT = COUNT;
}
}
ここでの魔法の一部は、ダミー テーブルを作成し、そこに 1 つのレコードを挿入することです。私の場合、データベースが Oracle であるため、dual1 という名前を付けましたが、ダミー テーブルを何と呼ぶかは重要ではないと思います。
@Entity
@Table(name="DUAL1")
public class Dual1 {
@Id
Long ID;
}
ダミー レコードを挿入することを忘れないでください。
SQL> insert into dual1 values (1);