Oracleのリストの最大数を考慮したHibernate Criteria

※この投稿は、丸太式 Advent Calendar 2014の2日目です

DBMSのOracleには、IN句等に渡せるリストの最大長に制限があることは皆さんご存じだと思います。
ご存じ無い方のために例を示しますと、以下の二つのSQLでは、前者は成功し、後者は失敗します。

SELECT * FROM AnyTable WHERE AnyColumn IN (1, 2, 3, (), 1000);
SELECT * FROM AnyTable WHERE AnyColumn IN (1, 2, 3, (), 1000, 1001);
-- →ORA-01795: リストに指定できる式の最大数は1000です。

そもそもこんなSQLが発行されているのが大きな間違いですが、保守開発などどうしても大きく変更できないときは、このようにしてお茶を濁すことになります。

SELECT *FROM AnyTable WHERE AnyColumn IN (0から1000) OR AnyColumn IN (1001以降);

単純ですね。IN句を1000要素毎に分割してORでつないだだけです。

さて、ここでJavaからHibernateのCriteriaを使う場合に、Collectionの要素が1000件を超えるとやはりエラーとなってしまいます。
OracleのDialectでは対応出来ていないため、Criteriaに条件を指定する際には以下のように工夫する必要があります。

Collection<T> theCollection; // 要素数が1000を超えるかもしれないコレクション
Criteria criteria = session.createCriteria(AnyTable.class);
// criteria.add(Restrictions.in("AnyColumn", theCollection)); //←これではtheCollection.size() > 1000の場合にエラー
List<T> tmpList = new ArrayList<>(theCollection);
Junction disjunction = Restrictions.disjunction();
while (tmpList.size() > 1000) {
  List<T> subList = tmpList.subList(0, 1000);
  disjunction.add(Restrictions.in("AnyColumn", subList));
  tmpList = tmpList.subList(1000, tmpList.size());
}
if (!tmpList.isEmpty()) {
  disjunction.add(Restrictions.in("AnyColumn", tmpList));
}
criteria.add(disjunction);

面倒ですね。
コレクションを渡してdisjunctionを返すユーティリティメソッドを作っておくと多少は楽になるかもしれません。

まとめ

こんな事をするくらいなら、はじめからサブクエリを使いましょう。

コメントを残す