|
Erfahrungswerte und Tips für Hibernate 3.21. BasicsFür jedes Attribut bzw. jede Collection muss man ein get-/set-Methoden-Paar spendieren. Dieses muss aber nicht unbedingt public sein, private reicht auch. Das ist gut für den Fall, wenn man aus Design-Gründen eine public-Methode mit einer anderen Signatur oder mehr Intelligenz anbieten will. Z.B. zum Zugriff auf eine Collection public nur einen Iterator anbieten will, um die Kapselung zu erhalten. Hibernate leitet aus dem Java-Typ eines Attributs einen SQL-Typ ab. Das Default-Mapping der Typen ist hier in der Referenz beschrieben (im Buch "Java Persistence with Hibernate" ist es allerdings genauer beschrieben). Dort sollte man vor allem dann hineinschauen, um nachzulesen
Ein Attribut, das einen Geldbetrag
darstellt, soll nicht den Typ Double erhalten,
sondern BigDecimal (wenn nicht ein genereller
Money-Typ zur Verfügung steht). Bei einem Double treten Ungenauigkeiten in den
Nachkommastellen auf, die beim Vergleichen von Beträgen zu Fehlern führen. Jedes Entity, das durch Hibernate verwaltet wird, muss eine passende equals()- und hash()-Methode erhalten (vgl. Referenz ). Es liegt nahe, dazu den Primärschlüssel bzw. die ID zu verwenden:
2. Performance, insbesondere im BatchDie grundlegende Performance wird natürlich auf der Datenbank erzielt, durch den richtigen Einsatz von Indizes etc. Das ist normales Datenbank-Thema und wird hier nicht weiter erläutert. Hier geht es um Hibernate: werden performante SQL-Statements erzeugt, wie viel SQL-Statements werden erzeugt. In der Referenzdokumentation ist dem Thema Batch ein eigenes Kapitel gewidmet, ebenso im Buch. Das wiederhole ich hier nicht, sondern kommentiere es teilweise bzw. lenke den Augenmerk auf weitere Aspekte. 2.1 alle Objekte im Hauptspeicher?Ein Hauptproblem in der Batch-Verarbeitung (bzw. Massenverarbeitung) ist die Unmenge von Objekten, die erzeugt werden und bei naivem Programmieren zu einer out-of-memory-Exception führen (spätestens in der Produktion). Deswegen wird in der Referenzdokumentation bzw. im Buch vorgeschlagen, in regelmässigen Intervallen ein flush() und clear() durchzuführen, um die Objekte aus der Session zu entfernen, so dass sie der GC aufräumen kann (Codezitat aus der Referenzdokumentation) : Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); ScrollableResults customers = session.getNamedQuery("GetCustomers") .setCacheMode(CacheMode.IGNORE) .scroll(ScrollMode.FORWARD_ONLY); int count=0; while ( customers.next() ) { Customer customer = (Customer) customers.get(0); customer.updateStuff(...); if ( ++count % 20 == 0 ) { //flush a batch of updates and release memory: session.flush(); session.clear(); } } tx.commit(); session.close(); Das ist absolut notwendig, reicht aber nicht. Aus der Datenbank-Programmierung mit DB2 und Oracle ist mir das Pattern bekannt, daß man in regelmässigen Abständen ein COMMIT durchführen sollte - das gilt programmiersprachenunabhängig. Damit wird das obige Beispiel wesentlich komplexer:
Eine Möglichkeit dafür ist, am Anfang des Batches mit einem HQL- oder
Criteria-Query einen Arbeitsvorrat zu bestimmen und diesen Arbeitsvorrat dann
portionsweise abzuarbeiten. Der Arbeitsvorrat kann natürlich nicht die Menge der
zu bearbeitenden Objekte sein (diese würden alle instanziiert, vgl. oben:
out-of-memory-Exception), sondern die Menge der IDs der zu bearbeitenden Objekte (Good Bye,
Objektorientierung). Bei einem Criteria-Query erreicht man das mit einer
Projection: 2.2 Abfragen und Updates gemischtEin generelles Problem für Persistenz-Frameworks ist: wenn eine Abfrage (z.B. Criteria oder HQL oder JPA-QL) gemacht wird, und im Cache des Frameworks sind geänderte Objekte, die noch nicht auf die Datenbank geschrieben wurden - wie soll die Richtigkeit des Abfrageergebnisses gewährleistet werden? Die Antwort der Hibernate-Autoren und des JPA-Gremiums ist: falls das Abfrageergebnis durch die Objekte im Cache (der Hibernate-Session, des persistence context) beeinflusst werden könnte, wird vor dem Absetzen des SQL-Query die Datenbank mit einem automatischen flush synchronisiert. Nach meiner Beobachtung führt Hibernate hier ein flush() durch, auch wenn die zu ändernden Tabellen garnichts mit den in der Abfrage berührten Tabellen zu tun haben. Das mag generell OK sein wegen undurchschaubarer Fernwirkungen, aber bremst einen Batchlauf gehörig aus. Deswegen
2.3 Abfragebeschleunigung durch Batch_Fetch_Size und JOIN FETCHNehmen wir folgenden Ausschnitt aus einem Modell und eine Abfrage darauf, die Angestellte einschliesslich ihrer Job-Bezeichnung anlistet.
SELECT job0_.job_id as job1_1_0_ ,...alle Attribute von JOB
Wird default_batch_fetch_size z.B. auf 10 gesetzt, holt Hibernate auf einen Rutsch 10 Job-Objekte: SELECT job0_.job_id as job1_1_0_ ,...alle Attribute von JOB
Die Einstellung wirkt übrigens auch im JPA-Modus - in der
persistence.xml ist einfach ein Hibernate-spezifischer Eintrag
enthalten: Die Assoziation vom Entity Angestellter zum Entity Job ist, wie üblich, mit lazy loading
definiert. Das Optimale für diese Liste von Angestellten ist natürlich, wenn die Jobs
mit dem gleichen SQL wie die Angestellten gelesen werden. Das geschieht (sowohl
in HQL als auch JPA-QL) mit dem
Schlüsselwort JOIN FETCH: Während default_batch_fetch_size eine generelle Einstellung ist, wird hier der Query optimiert, so dass die Job-Objekte eager geladen werden.
|