2010-05-03 7 views
13

Ich muss meine Web-App mit wirklich riesigen Datensätzen arbeiten lassen. Im Moment bekomme ich entweder OutOfMemoryException oder Ausgabe, die 1-2 Minuten generiert wird.Wie behandelt man große Datenmengen mit JPA (oder zumindest mit Hibernate)?

Sagen wir es einfach und nehmen wir an, dass wir 2 Tabellen in DB haben: Worker und WorkLog mit ungefähr 1000 Reihen in der ersten und 10 000 000 Reihen in der zweiten. Letztere Tabelle hat mehrere Felder einschließlich "workerId" und "Stunden Worked" Felder unter anderem. Was wir brauchen, ist:

  1. zählen insgesamt Stunden von jedem Benutzer gearbeitet;

  2. Liste der Arbeitsperioden für jeden Benutzer.

Der einfachste Ansatz (IMO) für jede Aufgabe im Klar SQL ist:

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 

So habe ich zwei Fragen :

  1. wie ich jeden meiner Ansätze mit JPA (oder zumindest mit Hibernate) umsetze;
  2. Wie würden Sie mit diesem Problem umgehen (mit JPA oder Hibernate natürlich)?
+1

Versuchen Sie, einen Bericht zu erstellen, oder versuchen Sie, eine Reihe von Objekten zu laden? Wenn Sie nur versuchen, einen Bericht zu erstellen, dann tun Sie es in SQL, wie Sie sagten, und damit fertig sein. – Zak

+0

@Zak: Ich habe eine Web-Anwendung in Jpa + Spring + Jsf, die funktioniert. Aber seine Leistung sollte besser sein. Und was noch wichtiger ist, es sollte in der Lage sein, viel größere Datenmengen zu verarbeiten, als es momentan möglich ist. 1) Es gibt ein Problem mit der ersten Abfrage, dass ich nicht weiß, wie man es in 'hql' oder' jpa query language' schreibt. Ich möchte nicht einfach sql verwenden, ich bin es ein letzter Ausweg. 2) Das Problem mit der zweiten Abfrage ist, dass ich nicht weiß, wie man die Abrufgröße in 'JPA' einstellt, und ich weiß auch nicht, wie ich diese Situation mit' JPA' behandeln soll: Es gibt keine Schleife durch die Ergebnismenge. Ich weiß, wie man den nächsten Abruf ausführt. – Roman

Antwort

13

nehme an, dass wir zwei Tabellen in DB: Arbeiter und WorkLog mit etwa 1000 Zeilen in der ersten und 10 000 000 Zeilen in der zweiten

Für hohe Mengen wie diese, meine Empfehlung wäre The StatelessSession interface aus dem Ruhezustand zu verwenden:

Alternativ Ruhezustand einen Befehlsorientierte API, die verwendet werden kann, um Daten an und von Streaming die Datenbank in Form von freistehenden Objekte. A StatelessSession hat keinen Persistenzkontext, der damit verbunden ist und bietet nicht viele der höheren Lebenszyklus-Semantik . In insbesondere, eine statusfreie Sitzung implementiert nicht einen Cache der ersten Ebene noch interagieren mit jedem Abfrage-Cache der zweiten Ebene oder . Es implementiert Transaktions-Write-Behind oder automatische Dirty-Überprüfung. Operationen durchgeführt mit einer statusfreien Sitzung nie zu assoziierten Instanzen kaskadieren. Sammlungen werden von einer zustandslosen Sitzung ignoriert. Operationen, die über eine statusfreie Sitzung ausgeführt werden, umgehen das Ereignismodell und die Interzeptoren von Hibernate. Aufgrund von das Fehlen eines Cache der ersten Ebene, Stateless-Sitzungen sind anfällig für Daten Aliasing-Effekte . Eine statusfreie Sitzung ist eine untergeordnete Abstraktion , die viel näher an der zugrunde liegenden JDBC ist.

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

In diesem Codebeispiel die Customer Instanzen von der Abfrage zurückgegeben werden sofort abgelöst. Sie sind nie mit jeder Persistenz verbunden Kontext.

Die insert(), update() und delete() Operationen durch die StatelessSession Schnittstelle definiert sind als direkte Datenbank Zeilenebene Operationen sein. Sie ergeben die sofortige Ausführung eines SQL INSERT, UPDATE oder DELETE jeweils. Sie haben unterschiedliche Semantiken zu den und delete() Operationen definiert durch die Schnittstelle.

+0

@Pascal Thivent: Danke für die Antwort! Über Volumes: Ich kenne die echten Volumes nicht, ich habe nur das Maximum angegeben (meiner Meinung nach basiert das auf etwas Kenntnis der Domain). Vielleicht ist das wirkliche Volumen 10-100 mal weniger und IMHO wird die Lösung für diese Volumes auch in Ordnung sein. – Roman

+0

Wissen Sie, was sie genau bedeuten, wenn "Stateless Sessions anfällig für Daten-Aliasing-Effekte sind"? Vielen Dank. –

+0

Dies ist in keiner Weise schneller.Tatsächlich ist es ** extrem ** langsam und viel weniger leistungsfähig als die übliche Verwendung von 'EntityManager'. – Blauhirn

1

Raw SQL sollte nicht als letzter Ausweg betrachtet werden. Es sollte immer noch als Option in Betracht gezogen werden, wenn Sie auf der JPA-Ebene "Standard" beibehalten möchten, nicht jedoch auf der Datenbankebene. JPA unterstützt auch systemeigene Abfragen, bei denen die Zuordnung zu Standard-Entitäten weiterhin für Sie erfolgt.

Wenn Sie jedoch eine große Ergebnismenge haben, die nicht in der Datenbank verarbeitet werden kann, sollten Sie einfach nur JDBC verwenden, da JPA (Standard) das Streaming großer Datenmengen nicht unterstützt.

Es ist schwieriger, Ihre Anwendung auf verschiedene Anwendungsserver zu portieren, wenn Sie JPA-implementierungsspezifische Konstrukte verwenden, da die JPA-Engine in den Anwendungsserver eingebettet ist und Sie möglicherweise kein Steuerelement für den verwendeten JPA-Anbieter haben.

+0

dies. Ich fand, dass das Ausführen einer Verbindungsabfrage manuell ('session.doWork' oder so) in der Tat die schnellste ist, die Sie erhalten können – Blauhirn

+0

der Standard EntityManager hat keine' doWork' Operation. –

+0

Ja, deshalb habe ich 'session' geschrieben, das Sie über' entityManager.unwrap (Session.class); 'erhalten können. Idk wenn das aber ein schlechter Programmierstil ist. Ich denke, man könnte auch eine 'sessionFactory' schreiben Bean – Blauhirn

0

Ich benutze so etwas und es funktioniert sehr schnell. Ich hasse es auch, natives SQL zu verwenden, da unsere Anwendung in jeder Datenbank funktionieren sollte.

Folowing results in eine sehr optimierte sql und returns Liste der Datensätze, die Karten sind.

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(); 
+0

Optimiert? Kannst du das erklären? – nalply

0

Ich stimme zu, dass die Berechnung auf dem Datenbankserver die beste Option in dem von Ihnen erwähnten Fall ist. HQL und JPAQL können diese beiden Anfragen bearbeiten:

1)

select w, sum(wl.hoursWorked) 
from Worker w, WorkLog wl 
where w.id = wl.workerId 
group by w 

oder, wenn der Verein zugeordnet ist:

select w, sum(wl.hoursWorked) 
from Worker w join w.workLogs wl 
group by w 

beide oder die Rückkehr Sie Liste, in der Object [] s sind Arbeiter und Lang. Oder man könnte auch „dynamische Instanziierung“ Abfragen verwenden, um das einpacken, zum Beispiel:

select new WorkerTotal(select w, sum(wl.hoursWorked)) 
from Worker w join w.workLogs wl 
group by w 

oder (je nach Bedarf) wahrscheinlich sogar nur:

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 ist nur eine normale Klasse. Es muss passende Konstruktoren haben.

2)

select w, new Period(wl.start, wl.hoursWorked) 
from Worker w join w.workLogs wl 

dies kehren Sie ein Ergebnis für jede Zeile in der Tabelle WorkLog ... Das new Period(...) Bit wird „dynamische Instanziierung“ genannt und wird verwendet, Tupel aus dem Ergebnis in Objekte umwickeln (leichterer Konsum).

Für Manipulation und allgemeine Verwendung empfehle ich StatelessSession als Pascal weist darauf hin.

0

Es gibt verschiedene Techniken, die miteinander in Verbindung verwendet werden können, müssen, um erstellen und Abfragen für große Datenmengen zu manipulieren, wo Speicher eine Begrenzung ist:

  1. Verwenden setFetchSize (etwas Wert, vielleicht 100+) als Standard (über JDBC) ist 10. Dies ist mehr über die Leistung und ist der größte zusammenhängende Faktor davon. Kann in JPA mit QueryHint von Provider (Hibernate, etc) durchgeführt werden. Es gibt (aus welchen Gründen auch immer) keine JPA Query.setFetchSize(int) Methode.
  2. Versuchen Sie nicht, das gesamte Ergebnis für 10K + Datensätze zu marshall. Es gibt mehrere Strategien: Verwenden Sie für GUIs Paging oder ein Framework, das Paging durchführt. Betrachten Sie Lucene oder kommerzielle Suchmaschinen/Suchmaschinen (Endeca, wenn das Unternehmen das Geld hat). Um Daten irgendwo zu senden, streamen Sie sie und leeren Sie den Puffer alle N Datensätze, um zu begrenzen, wie viel Speicher verwendet wird. Der Stream kann in eine Datei, ein Netzwerk usw. geleert werden. Denken Sie daran, dass JPA JDBC verwendet und JDBC die Ergebnismenge auf dem Server speichert, wobei nur N-Zeilen in einer Satzgruppe gleichzeitig abgerufen werden. Diese Aufschlüsselung kann manipuliert werden, um das Ausspülen von Daten in Gruppen zu erleichtern.
  3. Überlegen Sie, was der Anwendungsfall ist. In der Regel versucht eine Anwendung, Fragen zu beantworten. Wenn die Antwort darin besteht, bis zu 10K + Zeilen zu entfernen, sollte das Design überprüft werden. mit Indizierung Suchmaschinen wie Lucene wieder, zu prüfen, um die Abfragen verfeinern, betrachten BloomFilters als enthält Kontroll Caches mit Nadeln in Heuhaufen zu finden, ohne auf die Datenbank zu gehen usw.
3

Es scheint, dass Sie auch diese mit Eclipse tun können . diese Check: 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) { ... } 
+0

setHint-Methode, die nicht definiert ist. –

2

Diese blog post kann auch helfen. Es faßt den Ansatz mit zustandsloser Sitzung zusammen und fügt einige zusätzliche Hinweise hinzu, z. wie man Ergebnisse mit JAX-RS streamen kann.

Verwandte Themen