Webアプリを非常に巨大なデータセットで動作させる必要があります。現時点では、OutOfMemoryExceptionまたは1〜2分で生成されている出力のいずれかが表示されます。
簡単に言えば、DBに2つのテーブルがあるとします。Worker
とWorkLog
で、最初のテーブルに約1000行、2番目のテーブルに10000000行あります。後者のテーブルには、「workerId」フィールドや「hoursWorked」フィールドなど、いくつかのフィールドがあります。必要なものは次のとおりです。
各ユーザーの合計作業時間をカウントします。
各ユーザーの作業期間のリスト。
プレーンSQLの各タスクの最も簡単なアプローチ(IMO)は次のとおりです。
1)
select Worker.name, sum(hoursWorked) from Worker, WorkLog
where Worker.id = WorkLog.workerId
group by Worker.name;
//results of this query should be transformed to Multimap<Worker, Long>
2)
select Worker.name, WorkLog.start, WorkLog.hoursWorked from Worker, WorkLog
where Worker.id = WorkLog.workerId;
//results of this query should be transformed to Multimap<Worker, Period>
//if it was JDBC then it would be vitally
//to set resultSet.setFetchSize (someSmallNumber), ~100
だから、私は2つの質問があります:
dBに2つのテーブルがあるとします。最初のテーブルに約1000行、2番目のテーブルに10000 000行のWorkerとWorkLogです。
このような大量の場合、Hibernateの StatelessSession
インターフェイス を使用することをお勧めします。
あるいは、Hibernateは、分離オブジェクトの形式でデータベースとの間でデータをストリーミングするために使用できるコマンド指向のAPIを提供します。
StatelessSession
には永続コンテキストが関連付けられておらず、高レベルのライフサイクルセマンティクスの多くを提供していません。特に、ステートレスセッションは、第1レベルのキャッシュを実装せず、第2レベルまたはクエリキャッシュと対話しません。トランザクションの後書きまたは自動ダーティチェックは実装されていません。ステートレスセッションを使用して実行される操作は、関連付けられたインスタンスにカスケードされることはありません。コレクションはステートレスセッションによって無視されます。ステートレスセッションを介して実行される操作は、Hibernateのイベントモデルとインターセプターをバイパスします。第1レベルのキャッシュがないため、ステートレスセッションはデータエイリアシングの影響を受けやすくなっています。ステートレスセッションは、基盤となるJDBCにはるかに近い低レベルの抽象化です。_StatelessSession session = sessionFactory.openStatelessSession(); Transaction tx = session.beginTransaction(); ScrollableResults customers = session.getNamedQuery("GetCustomers") .scroll(ScrollMode.FORWARD_ONLY); while ( customers.next() ) { Customer customer = (Customer) customers.get(0); customer.updateStuff(...); session.update(customer); } tx.commit(); session.close();
_このコード例では、クエリによって返された
Customer
インスタンスはすぐに切り離されます。それらは永続コンテキストに関連付けられることはありません。
StatelessSession
インターフェースによって定義されたinsert(), update()
およびdelete()
操作は、直接データベースの行レベルの操作と見なされます。その結果、SQL _INSERT, UPDATE
_またはDELETE
がそれぞれ即座に実行されます。それらは、Session
インターフェースによって定義されたsave(), saveOrUpdate()
およびdelete()
操作とは異なるセマンティクスを持っています。
EclipseLinkでもこれを実行できるようです。これを確認してください: http://wiki.Eclipse.org/EclipseLink/Examples/JPA/Pagination :
Query query = em.createQuery...
query.setHint(QueryHints.CURSOR, true)
.setHint(QueryHints.SCROLLABLE_CURSOR, true)
ScrollableCursor scrl = (ScrollableCursor)q.getSingleResult();
Object o = null;
while ((o = scrl.next()) != null) { ... }
この ブログ投稿 も役立ちます。ステートレスセッションでのアプローチを要約し、いくつかの追加のヒントを追加します。 JAX-RSを使用して結果をストリーミングする方法。
メモリが制限されている大規模なデータセットのクエリを作成および操作するには、互いに組み合わせて使用する必要があるいくつかの手法があります。
Query.setFetchSize(int)
メソッドはないようです。生のSQLは最後の手段と見なされるべきではありません。データベース層ではなく、JPA層で物事を「標準」に保ちたい場合は、それでもオプションと見なす必要があります。 JPAは、ネイティブクエリもサポートしており、標準エンティティへのマッピングを引き続き実行します。
ただし、データベースで処理できない大きな結果セットがある場合は、JPA(標準)が大きなデータセットのストリーミングをサポートしていないため、実際にはプレーンJDBCを使用する必要があります。
JPAエンジンはアプリケーションサーバーに組み込まれており、どのJPAプロバイダーを使用するかを制御できない可能性があるため、JPA実装固有の構造を使用すると、異なるアプリケーションサーバー間でアプリケーションを移植することが難しくなります。
あなたが言及した特定のケースでは、データベースサーバーで計算を行うことが最善の選択肢であることに同意します。 HQLとJPAQLは、これらのクエリの両方を処理できます。
1)
_select w, sum(wl.hoursWorked)
from Worker w, WorkLog wl
where w.id = wl.workerId
group by w
_
または、関連付けがマップされている場合:
_select w, sum(wl.hoursWorked)
from Worker w join w.workLogs wl
group by w
_
両方、またはObject []がWorkerとLongであるリストを返します。または、「動的インスタンス化」クエリを使用してそれをまとめることもできます。次に例を示します。
_select new WorkerTotal( select w, sum(wl.hoursWorked) )
from Worker w join w.workLogs wl
group by w
_
または(必要に応じて)おそらくただ:
_select new WorkerTotal( select w.id, w.name, sum(wl.hoursWorked) )
from Worker w join w.workLogs wl
group by w.id, w.name
_
WorkerTotalは単なるクラスです。一致するコンストラクターが必要です。
2)
_select w, new Period( wl.start, wl.hoursWorked )
from Worker w join w.workLogs wl
_
これにより、WorkLogテーブルの各行の結果が返されます... new Period(...)
ビットは「動的インスタンス化」と呼ばれ、結果のタプルをオブジェクトにラップするために使用されます(消費が容易)。
操作と一般的な使用法については、Pascalが指摘しているようにStatelessSessionをお勧めします。
私はこのようなものを使用していますが、非常に高速に動作します。また、アプリケーションはどのデータベースでも機能するはずなので、ネイティブSQLを使用するのも嫌いです。
次の結果は非常に最適化されたSQLになり、マップであるレコードのリストを返します。
String hql = "select distinct " +
"t.uuid as uuid, t.title as title, t.code as code, t.date as date, t.dueDate as dueDate, " +
"t.startDate as startDate, t.endDate as endDate, t.constraintDate as constraintDate, t.closureDate as closureDate, t.creationDate as creationDate, " +
"sc.category as category, sp.priority as priority, sd.difficulty as difficulty, t.progress as progress, st.type as type, " +
"ss.status as status, ss.color as rowColor, (p.rKey || ' ' || p.name) as project, ps.status as projectstatus, (r.code || ' ' || r.title) as requirement, " +
"t.estimate as estimate, w.title as workgroup, o.name || ' ' || o.surname as owner, " +
"ROUND(sum(COALESCE(a.duration, 0)) * 100 / case when ((COALESCE(t.estimate, 0) * COALESCE(t.progress, 0)) = 0) then 1 else (COALESCE(t.estimate, 0) * COALESCE(t.progress, 0)) end, 2) as factor " +
"from " + Task.class.getName() + " t " +
"left join t.category sc " +
"left join t.priority sp " +
"left join t.difficulty sd " +
"left join t.taskType st " +
"left join t.status ss " +
"left join t.project p " +
"left join t.owner o " +
"left join t.workgroup w " +
"left join p.status ps " +
"left join t.requirement r " +
"left join p.status sps " +
"left join t.iterationTasks it " +
"left join t.taskActivities a " +
"left join it.iteration i " +
"where sps.active = true and " +
"ss.done = false and " +
"(i.uuid <> :iterationUuid or it.uuid is null) " + filterHql +
"group by t.uuid, t.title, t.code, t.date, t.dueDate, " +
"t.startDate, t.endDate, t.constraintDate, t.closureDate, t.creationDate, " +
"sc.category, sp.priority, sd.difficulty, t.progress, st.type, " +
"ss.status, ss.color, p.rKey, p.name, ps.status, r.code, r.title, " +
"t.estimate, w.title, o.name, o.surname " + sortHql;
if (logger.isDebugEnabled()) {
logger.debug("Executing hql: " + hql );
}
Query query = hibernateTemplate.getSessionFactory().getCurrentSession().getSession(EntityMode.MAP).createQuery(hql);
for(String key: filterValues.keySet()) {
Object valueSet = filterValues.get(key);
if (logger.isDebugEnabled()) {
logger.debug("Setting query parameter for " + key );
}
if (valueSet instanceof Java.util.Collection<?>) {
query.setParameterList(key, (Collection)filterValues.get(key));
} else {
query.setParameter(key, filterValues.get(key));
}
}
query.setString("iterationUuid", iteration.getUuid());
query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
if (logger.isDebugEnabled()) {
logger.debug("Query building complete.");
logger.debug("SQL: " + query.getQueryString());
}
return query.list();