Hibernate
Hibernate Links
Datenbank
Für den Anfang ist hsqldb sehr gut geeignet. Alle Daten werden dabei in einem Ordner namens "data" im Projektordner als Plaintext abgelegt.
Erste Schritte
Wir wollen die Klasse hibernate3 in eine Datenbank persistieren bzw die Datenbank auslesen und den Inhalt in dieser Klasse abspeichern. Dazu geben wir der Klasse als erstes einige Attribute und passenden Getter- und Setter-Methoden für all diese Attribute. Zusätzlich noch ein id Attribut vom Type long für Hibernate, das einen privaten Setter hat und nur von Hibernate benutzt werden wird.
Hibernation logt über log4j, welches über eine konfiguriert werden sollte.
Hibernation selbst braucht auch eine Configdatei, . In dieser wird definiert wo die Datenbank liegt. Außerdem wird für jede Klasse, die in die Datenbank gemappt werden soll, ein Verweis auf eine Datei aufgeführt, in der das Mapping zwischen der Klasse und einer (oder mehrerer) Tabelle(n) gelistet werden soll.
Ein Verweis auf eine solche Datei sieht z.B. so aus:
<mapping resource="foo/bar/MyOtherClass.hbm.xml"/>
Ein Mapping für die Klasse "de.tgunkel.de.hibernate3" mit den Attributen "rot", "gruen" und "blau", die auf die Tabelle "MeineFarben" (Achtung: Keine Leerzeichen in Tabellennamen!) und dort die Spalten "RotAnteil", "GruenAnteil" und "BlauAnteil" gemappt werden sieht z.B. so aus:
<id name="id" column="uid" type="long">
<generator class="increment"/>
</id>
<property name="rot" column="RotAnteil" />
<property name="gruen" column="GruenAnteil" />
<property name="blau" column="BlauAnteil" />
</class>
Bevor man Hibernate wirklich nutzen kann, braucht man erst einmal Zugriff auf eine Hibernate-Session. Am besten schreibt man sich dafür eine kleine , die einem bei Bedarf eine Session zur Verfügung stellt.
Ganz am Ende müssen wir noch einige hsql spezifische Sachen aufrufen (braucht man für andere Datenbanken so nicht), das lassen wir gleich auch durch die Managerklasse erledigen.
Diese kann dann auch noch die Hibernate Session schließen
Dazwischen kann man jetzt Hibernate benutzen um Daten in eine Datenbank zu sichern:
Farbe gruen=new Farbe(0,255,0);
Farbe blau=new Farbe(0,0,255);
// Transaction to write to the db via Hibernation
Transaction myTransaction = session.beginTransaction();
session.save(rot);
session.save(gruen);
session.save(blau);
myTransaction.commit();
Oder auch lesen:
myTransaction=session.beginTransaction();
List result = session.createQuery("from Farbe").list();
myTransaction.commit();
for (int i = 0; i<result.size(); i++) {
Farbe neueFarbe = (Farbe) result.get(i);
System.out.println("Farbe: " + neueFarbe.toString());
}
Nicht vergessen mögliche Exceptions zu fangen
Dabei ist Farbe kein Tabellenname sondern ein Klassenname der ein Hibernatemapping auf eine Tabelle hat.
Hibernate Objekte können folgende Zustände haben:
persistent | transient | detached |
Es gibt ein Java Objekt und ein entsprechenden Eintrag in der Datenbank. Hibernate weiß, dass beide zusammengehören. Z.B. weil er die Objekte aus der Datenbank geladen hat. Änderungen an den Java Objekten werden automatisch mit der DB abgeglichen (man soll hier NICHT session.update() aufrufen). Transistente Objekte werden persistent wenn man save(), persist() oder saveOrUpdate() aufruft. Persistente werden transient durch session.delete(). | Noch nie persistent gewesen, nicht mit einer Session verknüpft. | Es gibt ein Java Objekt und ein entsprechenden Eintrag in der Datenbank. Hibernate weiß aber nicht mehr, dass beide zusammengehören. Das passiert wenn man die Session schließt und wieder eine neue öffnet. |
Ein Objekt ist auch dann noch persistent, wenn man Attribute des Objekts verändert hat. Diese Änderungen werden auch automatisch in die Datenbank persistiert.
Version
Wenn man mit Hibernate Daten aus der DB geladen hat, sollte man immer damit rechnen, dass ein anderer Thread (ebenfalls) über Hibernate die Daten gelesen und verändert hat. In diesem Fall darf man natürlich die eigenen Daten nicht einfach in die DB zurückschreiben, da sie ja bereits veraltet sind. Hibernate mit einen Mechanismus an, um diese Situation zu entdecken. Hibernate optimistic locking
Version mit Version Spalten
In der DB wird eine Versionsspalte hinzugefügt. Man mappt die Spalte z.B. so
oder über eine Annotation
public Integer getVersion();
Immer wenn Hibernate die Daten zurückschreibt erhöht es den Wert in der Spalte. Wurde der Wert in der DB schon vorher erhöht, gab es einen Konflikt. Das kann man noch gegen Änderungen anderer Anwendungen absichern, indem ein Trigger die Version Spalte bei jeder Änderung erhöht, die nicht schon eine Änderung der Version Spalte beinhaltet.
Version mit Timestamp Spalte
Eine andere Möglichkeit ist, statt einer Version Spalte eine Spalte mit einem Timestamp einzuführen, in der die exakte Zeit der letzten Änderung geführt wird. Hibernate setzt die Zeit bei jedem Schreibvorgang automatisch auf die aktuelle Zeit. Wurde die Zeit nicht durch uns hochgesetzt, erkennen wir eine konkurrierende Änderungen. Eine solche Spalte ist natürlich etwas weniger technisch und stößt damit leichter auf fachliche Akzeptanz. Hinzu kommt, dass sie eine fachliche Information, die Zeit der letzten Änderung liefert. Allerdings besteht das theoretische Risiko, dass zwei gleichzeit stattfindende Änderungen zufällig den selben Timestamp setzen und damit sich gegenseit nicht erkennen können.
public Timestamp getUpdateTimeStamp() {
Man kann das noch etwas ergänzen, damit auch Änderungen außerhalb von Hibernate die Zeit in dieser Spalte automatisch hochsetzen, indem wir einen Trigger erstellen. Man darf allerdings nicht bei jeder Änderung einfach den Wert hochsetzen, da Hibernate z.B. den Wert gerade selbst setzen möchte, um konkurrierende Änderungen zu erfassen. Würde man hier eingreifen würde das Fehlalarme durch Hibernate auslösen. Daher wird die Zeit der letzten Änderung nur dann durch den Trigger gesetzt, wenn das Änderung nicht schon eine Aktualisierung der Zeit beinhaltet.
BEFORE UPDATE ON foo.tableX
FOR EACH ROW
BEGIN
if :old.updatetimestamp>=:new.updatetimestamp then
select sysdate into :new.updatetimestamp from dual;
end if;
END;
/
show errors;
Mappings
Siehe auch xylax.net: Index of Relationships
Mögliche Probleme
Kommt es zu folgendem Fehler
- Gibt es die Datei wirklich?
- Liegt die Datei dort, wo auch die Klasse liegt?
Steht im Mapping auch der Names des Packages? Also
one-to-one
Eine Objekt der Klasse Foo hat ein Objekt vom Typ Bar
<one-to-one name="bar" class="Bar" cascade="all" />
</class>
In der Datenbank wird das dann so gelöst, dass man 2 Tabellen hat und der x. Eintrag in der einen Tabelle gehört zum x. Eintrag in der anderen Tabelle (bzw. der Eintrag mit der ID x)
many-to-one
Wie one-to-one, ein Objekt der Klasse Foo verweist auf ein Objekt vom Typ Bar. Jetzt ist es aber möglich dass zwei verschiedene Foo Objekte auf das gleiche Bar Objekt zeigen.
...
<many-to-one name="bar" class="Bar" column="bar_id" cascade="all" />
</class>
Gelöst wird das jetzt so dass in jeder Zeile der foo Tabelle eine Spalte ist die angibt welche ID aus der Bar Tabelle dazu passt.
one-to-many
Ein Objekt der Klasse Foo verweist auf eine Liste von Objekten vom Typ Bar. In Java wird das z.B. über ein set gelöst:
public Set getBar();
public void setBar(Set bar);
Im Hibernate Mapping sieht das dann so aus:
<set name="myBars">
<key column="FooID" />
<one-to-many class="Bar" />
</set></class>
Damit hat man jetzt ein Tabelle mit allen Foo, und eine Tabelle mit allen Bar. In der Bar Tabelle gibt es eine zusätzliche Spalte in der steht zu welchem Foo (welcher Foo-ID) die aktuelle Bar Zeile gehört. Wenn einem Foo mehreren Bar zugeordnen werden sollen, verweisen einfach mehrere Bar Zeilen auf diese Foo-ID.
Damit die Bar automatisch zum passenden Foo verweisen braucht man noch eine add Methode die ein einzelnes Bar hinzufügt.
{
this.myBars.add(b);
}
Problem: Beim Hinzufügen wird erst ein neues Bar mit leerem Verweis erzeugt und dann per Update der Verweis gesetzt. Für kurze Zeit ist der Verweis daher null, was gegen not null constrains verstossen wird. Siehe auch Hibernate Parent/Child und den nächsten Abschnitt über inverse und mappedBy.
inverse / mappedBy
In Hibernate kann nicht nur das Mapping einer Tabelle auf eine Klasse abgebildet werden, sondern auch die Beziehung zwischen zwei Tabellen / Klassen. Ein klassisches Beispiel:
VATER
V_OID | VNAME |
---|---|
11 | Heinz |
12 | Juergen |
13 | Johan |
SOHN:
S_OID | SNAME | REF_V_OID |
---|---|---|
362 | Paul | 11 |
363 | Lena | 12 |
364 | Sandra | 11 |
365 | Andrea | 11 |
Hierbei wird jedem der Söhne per ref_v_oid ein Vater zugewiesen (many-to-one) und dadurch auch indirekt jedem Vater eine Menge von Söhnen (one-to-many). Per SQL stellt man so leicht den Zusammenhang her:
Die entsprechende Java Klassen sehen dann so
public class Sohn
{
long s_oid;
String sname;
vater vater;
@Id
@SequenceGenerator(name="myseq", sequenceName = "foo.my_seq", allocationSize=1)
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="myseq")
public long getS_oid() {
return s_oid;
}
public void setS_oid(long sOid) {
s_oid = sOid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
@ManyToOne
@JoinColumn(name="ref_v_oid")
public vater getVater() {
return vater;
}
public void setVater(vater vater) {
this.vater = vater;
}
}
und so aus
public class Vater
{
long v_oid;
String vname;
List<sohn> soehne;
@Id
@SequenceGenerator(name="myseq", sequenceName = "foo.my_seq", allocationSize=1)
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="myseq")
public long getV_oid() {
return v_oid;
}
public void setV_oid(long vOid) {
v_oid = vOid;
}
public String getVname() {
return vname;
}
public void setVname(String vname) {
this.vname = vname;
}
@OneToMany(mappedBy="vater")
@JoinColumn(name="ref_v_oid")
public List<sohn> getSoehne() {
return soehne;
}
public void setSoehne(List<sohn> soehne) {
this.soehne = soehne;
}
}
Während die Söhne genau wie die entsprechende Datenbanktabelle angebildet wurde, unterscheidet sich die Vater Tabelle in einem wichtigen Punkt: Sie enthält eine Liste aller Söhne. Diese Information ist in der Datenbank aber nur indirekt verfügbar (nämlich indem man alle Söhne sucht, deren Referenz auf den entsprechenden Vater Eintrag verweisen). Sofern man im Java Code diese bidirektionale Beziehung zwischen Vater und Söhnen nicht benötigt, sollte man erwägen, darauf auch in den Java Klassen zu verzichten und sich die Informationen gegebenenfalls über eine SQL oder HQL Statement zu beziehen.
Verzichtet man darauf jedoch nicht, muss man sich fragen, wie man in Java die Beziehung zwischen Vater und einem neuen Sohn herstellt.
(A) Man kann dem Sohn einen Vater zuweisen
(B) Man kann dem Vater einen Sohn hinzufügen
Aus Java Sicht sollte man tunlichst beides durchführen und sicherstellen, dass man dabei nicht versehentlich Widersprüche einfügt.
Doch wann und wie schreibt Hibernate diese Änderungen zurück in die Datenbank? Die Antwort hängt davon ab, wie man die Beziehung gemappt hat. Mappt man beide Seite einfach mit one-to-many und many-to-one und gibt jeweils die joinColumn an, führt sowohl Variante A als auch Variante B dazu, dass die Änderungen persisiert werden. Allerdings werden so in einigen Situationen unnötige Statements generiert. Ein neuer Sohn löst z.B. erst ein Sohn Insert Statement aus (bei dem die Vater Beziehung schon richtig ist) und dann ein Update auf die Söhne um die Vater Beziehung erneut zu setzen (diesmal aus Vater Sicht). Erzeugt man viele neue Objekte, bremst das die Geschwindigkeit schnell unter 50%.
Besser ist es daher, im Hibernate Mapping einem der beiden Klassen die Verantwortung für die Beziehung zu übertragen. Hier
wird zum Beispiel in der Vater Klasse die Beziehung so markiert, dass die Verantwortung bei dem vater Attribut der anderen Klassen liegt. Hier also Sohn.vater. Das spart unnötige SQL Statements, allerdings werden Änderungen (nur) an der Söhne Liste des Vater (B) nicht mehr persistiert. Solange man in Java immer beide Seite der Beziehung pflegt (A) (B), hat man hier aber kein Problem.
Das Gegenstück ohne Annotations ist übrigens
Achtung: Setzt man in dem Mapping, welches die Verantwortung zwischen zwei Klassen gerade nicht übernehmen soll cascade Optionen, kann die Klassen dennoch gezwungen werden Änderungen an der Beziehung wieder zu persistieren. Das hier z.B.
ist vermutlich nicht besonders sinnvoll.
Siehe auch Hibernate inverse mapping.
Mapping Beispiel
Man hat zwei Tabellen
Parent:
idP | ParentName |
Child:
idC | ChildName | idrefP |
Beliebig viele Child Einträge können einem Parent Eintag zugewiesen werden, indem die idrefP Spalte auf eine idP zeigt.
So sieht ein Parent Objekt dann aus
class Parent
{
@Id
Long idV;
@OneToMany(mappedBy="myParent")
@JoinColumn(name="idrefP")
List<Child> myChildrend;
}
Die Klasse enthält eine Liste von allen Child Objekten, die auf die Klasse zeigen. Das ist ein Unterschied zur Tabelle, wo man eine Parent Zeile keine Referenz auf Ihre Child Objekte hat. Außerdem enthält die Klasse noch den Hinweis in welchem Attribut die Child Objekte die Referenz auf Ihre Parent Klasse halten und wie die Spalte in der Child Datenbanktabelle dafür heißt.
Die Child Klasse sieht so aus
class Child
{
@Id
Long idC;
@ManyToOne
@JoinColumn(name="idrefP", insertable=false, updatable = false )
Parent myParent;
}
Hier ist nur der Hinweis hinterlegt, welche Spalte in der Tabelle als Referenz auf das Vater Objekt benutzt wird.
Speichert man ein Parent Objekt werden auch alle Child Objekte gespeichert. Damit die Child Objekte umgekehrt nicht versuchen dann wieder Ihren Vater zu speichern setzt man
Vergisst man das erhält man
LazyInitializationException
Früher oder später wird man dieser Exception begegnen
Das Problem
- Man hat ein Vater Objekt aus der Datenbank geladen. Das Vater Objekt führt eine Liste mit Kind Objekten, die aber beim Laden des Vater Objektes nicht automatisch mitgeladen werden (lazy).
- Jetzt schließt man die dazugehörige Session irgendwie
- Sobald man auf ein die Liste von Kind Objekte zugreift, wird versucht, über die (geschlossene) Session die Kind Objekte aus der Datenbank nachzuladen.
- Dieses Problem tritt z.B. dann auf, wenn man beim Iterieren über eine Menge von Vater Objekten ab und zu session.clear() aufrufen möchte, um den Hibernate Speicherverbrauch durch Caches zu verringern.
Lösung
verbindet Vater wieder mit der Session, dann können auch wieder Kind Objekte bezogen werden.
Hibernate generiert IDs
@Column(name = "oid", nullable = false)
@SequenceGenerator(name="myFunnySequenceName4Hibernate", sequenceName="foo.seq_name_in_database", allocationSize=1)
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="myFunnySequenceName4Hibernate")
public Long getOid()
{
Angenommen man erzeugt sehr viele neue Objekte, die per Hibernate eine ID zuwiesen bekommen
{
Foo foo=new foo();
foo.setBar(...);
...
session.save(foo);
}
session.commit();
Die ID soll dabei aus einer Datenbank Sequenz gezogen werden
<class name="Foo" table="foo_table">
<id name="oid" column="OID">
<generator class="sequence">
<param name="sequence">FOO_OID_SEQ</param>
</generator>
</id>
...
Dann wird für jedes neu erzeugte Objekt eine ID aus der Sequenz ausgelesen (sobald für das Objekt session.save(foo) aufgerufen wird)
Das Auslesen einer neuen ID aus einer Sequenz ist zwar sehr performant, in der Summe machen sich diese Zugriffe allerdings dann doch irgendwann bemerkbar.
Um weiterhin eine Sequenz zu benutzen und trotzdem effizient viele IDs zu erhalten eignet sich der seqhilo Generator:
<class name="Foo" table="foo_table">
<id name="oid" column="OID">
<generator class="seqhilo">
<param name="sequence">FOO_OID_SEQ</param> <param name="max_lo">100</param> </generator> </id>
...
Damit werden immer 100 zusätzliche IDs aus der Sequenz ausgelesen und erst wenn diese 100+1 Stück aufgebraucht sind, wird die Sequenz wieder ausgelesen.
Mit Annotations kann man das gleiche mit AllocationSize erreichen. Hier bedeutet ein Wert von 101, bei jedem Zugriff auf die Sequenz Zugriff gleich 101 Elemente beziehen.
@SequenceGenerator(name="myseq", sequenceName = "blub.FOO_OID_SEQ", allocationSize=101)
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="myseq")
public long getId()
Mit beiden Variante erreicht man beim Erzeugen vieler Objekt einen riesigen Performancesprung.
Kleiner Nachteil: Auch wenn nur ein neues Objekt erzeugt wird, die Sequenz ist trotzdem um 100 erhöht.
Wenn man die Schleife noch so umschreibt, dass man nicht erst am Ende der Schleife in die Datenbank schreibt, sondern alle n Durchläufe (Hibernate Batch processing), dann ist es vielleicht geschickt, die max_lo Größe so zu wählen, dass die IDs auch für n Durchläufe ausreichen.
Hibernate Embedded IDs
Eigentlich sollte man versuchen, in jeder Tabelle einen technischen Primary Key zu haben. Manchmal trifft man aber auf Tabellen, die einen fachlichen Primary Key haben. Dieser geht dann häufig sogar über mehrere Spalten.
Beispiel, die Tabelle Foo
F1 | F2 | Data |
Als erstes wird der fachliche Schlüssel (F1,F2) in einer eigenen Klasse gemappt. Dabei sollte equals so überschrieben werden, dass zwei Schlüssel gleich sind, wenn die alle Spalten im Schlüssel gleich sind. Entsprechen muss dann auch hashCode überschrieben werden.
public class FooID implements Serializable
{
private Long f1;
private Long f2;
@Column(nullable = false)
public Long getF1() {
return this.f1;
}
@Column(nullable = false)
public Long getF2() {
return this.f2;
}
@Transient
@Override
public boolean equals(Object obj)
{
...
}
@Transient
@Override
public int hashCode()
{
...
}
}
Dann werden in der eigentlichen Klassen die Spalten des fachlichen Primärschlüssels nicht mehr gemappt, sondern nur eine Referenz auf das Schlüsselobjekt eingetragen, welche mit EmbeddedId markiert wird.
public class Foo
{
private FooID id;
private String data:
@EmbeddedId
public ViolationID getId() {
return id;
}
public String getData() {
}
}
Hibernate enum
Man kann in einem persistierten Objekt auch einen enum als Typ verwenden:
public MyEnumType getStatus() { ... }
Hibernate Query Langugage (HQL)
Bind parameters
- named parameter
q.setString("name", "Bar");
Iterator foos = q.iterate();
- positional parameter
q.setString(0, "Bar");
Iterator foos = q.iterate();
- named parameter list
names.add("Bar1");
names.add("Bar2");
Query q = sess.createQuery("from MyFoo foo where foo.name in (:namesList)");
q.setParameterList("namesList", names);
List foos = q.list();
HQL Join
Angenommen man hat zwei Tabellen t1 und t2 und zwei Klassen C1 und C2 mit Hibernate Mappings. Dann kann man
oder
schreiben. Man kann dann auch immer ein ein cartesisches Produkt bilden
Wenn es zwischen t1 und t2 eine Beziehung über einen Schlüssel gibt, kann man den auch im Mapping der beiden Klassen abbilden. Nehmen wir weiter an, C1.ref sei eine gemappte Referenz auf C2 Klassen. Dann kann man auch
schreiben, um C1 und alle über ref verknüpften C2 Objekte zu beziehen (ohne eine explizite Verknüpfungsbedingung angeben zu müssen). Man bekommt dann einen Array zurück, in dem in einer Zeile die jeweils verknüpften Objekten enthalten sind. Wenn man jetzt alle C1 ausfiltern möchte, die auf ein C2 verweisen, welches ein price Attribute ungleich null hat, muss man nicht mal mehr einen Join formulieren. Man kann direkt auf die Referenz Bedingungen abfragen, und Hibernate führt die notwendigen Joins automatisch aus
So kann man erzwingen, dass alle verknüpften Objekte sofort mitgeladen werden
Man kann in einer HQL Query auch bestimmen, welche Objekte oder Attribute man zurückgeliefert bekommt
Objekte löschen
Wenn man über HQL Objekte löschen will, geht das eigentlich so
Das kann mindestens an zwei Dingen scheitern:
- executeUpdate() gibt es in älteren Hibernate Versionen noch nicht
- Außerdem erlauben ältere Hibernateversionen nicht alle Statements. Fehlermeldung:
Ein Workarround ist dann, die zu löschenden Objekte erst mal mit Hibernet auszulesen und dann mit delete zu entfernen
Transaction myTransaction = mySession.beginTransaction();{ @SuppressWarnings("unchecked") List<Foo> lSearchResult= (List<Foo>) query.list(); Iterator<Foo> i=lSearchResult.iterator(); while(i.hasNext()) mORSPropertySession.delete(i.next());}
Oder natürlich einfach SQL statt HQL benutzen.
Materialized View per HQL aktualisieren
lQuery.executeUpdate();
Hibernate SQL Queries
Man kann über Hibernate auch normal SQL Queries ausführen lassen.
SQLQuery sqlQuery=lSession.createSQLQuery(sqlStrng);
sqlQuery.list();
Sofern man nur eine Spalte selektiert hat, erhalt man als Ergebnis
zurück, hat man mehr als eine Spalte selektiert, erhält man
zurück. Die Liste entspricht dabei den Zeilen, das Object Array den Spalten.
Ein großer Nachteil an dieser Variante ist natürlich, dass man das Ergebnis selbst wieder auf Java Objekte abbilden muss. Aber auch dafür gibt es eine Unterstützung.
In einem ersten Schritt kann man den selektierten Spalten schon mal einen Typ zuweisen. Die Zuweisung erfolgt über den Namen, den sie im SQL Statement zugewiesen bekommen haben
sqlQuery.addScalar("name", Hibernate.STRING);
sqlQuery.addScalar("appleid", Hibernate.LONG);
...
Wenn die gelesenen Spalten den vollständigen Inhalt einer Hibernate gemappten Tabelle wiedergeben, kann man das Ergebnis auch direkt in Objekte dieser Klasse wandeln lassen
sqlQuery.addEntity(Fruits.class);
Auch das Auslesen mehrerer Tabellen ist möglich:
sqlQuery.addEntity(Fruits.class);
sqlQuery.addEntity(Apples.class);
Dazu muss die Java Klasse aber auch eine Hibernate gemappte Klasse sein. Wenn sie das ist, kann man sie ja auch über HQL auslesen. Noch interessanter ist es daher, das Ergebnis in beliebige Java Beans schreiben zu können. Da die Spaltennamen aus dem SQL Statement immer in Großbuchstaben (UPPERCASE) zurückgegeben werden, wird man in den meisten Fällen den Namen mit addScalar überschreiben wollen, damit man die gewünschte Schreibweise erhält. Dabei kann man dann optional auch gleich noch den Datentyp anpassen
SQLQuery sqlQuery=lSession.createSQLQuery(sqlStrng);
sqlQuery.addScalar("id", Hibernate.LONG);
sqlQuery.addScalar("name");
sqlQuery.addScalar("fruitid", Hibernate.LONG);
...
sqlQ.setResultTransformer(Transformers.aliasToBean(MyBean.class));
Massenupdates
Hibernate Batch processing Wenn man in einer Schleife viele Hibernateobjekte schreibt, alle n Schleifendurchläufe
session.clear();
aufrufen und das hier setzten (n durch die tatsächliche Anzahl ersetzten)
Achtung, nach dem session.clear() sind alle Hibernate Objekte detachted. Man muss session.update(foo); machen, um sie wieder zu verbinden, sonst gibt es die Exception
Hibernate Criteria Queries
So kann man zählen wieviel Elemente es hiervon gibt
crit.setProjection(Projections.rowCount());
Integer c=(Integer) crit.uniqueResult();
Eine normale where Bedingung
Criterion my_age=Restrictions.gt("age", new Integer(42));
Criterion my_name=Restrictions.like("name", "Mr. Foo%");
LogicalExpression my_or=Restrictions.or(my_age, my_name);
crit.add(my_or);
List results=crit.list();
So kann man z.B. das Objekt mit der kleinsten ID finden:
ProjectionList projList = Projections.projectionList();
projList.add(Projections.min("id"));
crit.setProjection(projList);
Hibernate Annotations
Man benötigt die entsprechenden jars. Mindestens
ejb3-persistence.jar
Einige Annotations sind dem Hibernate jar, einige sind auch für EJB3 übernommen worden und andere sogar in die Java Persistence API. Falls eine Annotation aus mehr als einem der drei Quellen zu beziehen ist, dann die allgemeinere Quelle nehmen.
Die benutzte Session Factory muss aus einer AnnotationConfiguration sein.
In der cfg.xml verweist man statt auf eine hbm.xml
auf eine Klasse
Die Klasse muss dann für jede Spalte, die man aus der entsprechenden Tabelle mappen möchte ein gleichnamiges Attribut enthalten (erster Buchstabe sollte kleingeschrieben werden). Die kleinste Version einer solchen Klasse sieht ungefähr so aus
public class Bar
{
private Long id;
@ID
public Long getId()
{
return id;
}
}
Lässt man @Entity noch weg, ist die Klasse in Queries nur über vollständige Klassennamen zu erreichen, also
statt
Schreibt man die @ID Annotation vor eine Klassenvariable, greift Hibernate auf alle Attribute direkt zu, schreibt man sie vor einen Getter, wird für alle Attribute ein Getter und Setter benutzt. Der Getter und Setter muss genau die gleiche Groß- und Kleinschreibung in seinem Namen benutzen, wie das Attribut der Klasse (abgesehen vom ersten Buchstaben, der im Getter groß-, im Attribut kleingeschrieben werden muss).
Von allen anderen Attributen außer dem ID Attribut wird automatisch erwartet dass sie eine gleichlautende Entsprechung in der Datenbanktabelle haben. Mit dem Attribut
kann man Attribute und Methoden davon ausschließen.
Named Queries
Auch mit Annotations kann man Benannte Queries hinterlegen (auch wenn das in einer Java Klasse vermutlich weit weniger Sinn macht, als es das in einer hbm.xml gemacht hat).
@NamedQuery(name="foo", query="select a from Bar b where b.id = :myid")
public class Bar
{
...
So kann man aber nur eine Named Query pro Klasse haben, sonst gibt es eine
Wenn man mehr als eine Named Query benötigt geht das so;
@NamedQueries({
@NamedQuery(name = "a", query = "select a from A"),
@NamedQuery(name = "b", query = "SELECT b from B")
})
public class Bar
{
...
Exception Handling
Wenn man über Hibernate Daten auf der Datenbank verarbeitet, sollte man immer damit rechnen, dass eine Exeption auftreten könnte. Dann ist es wichtig, dass man die Daten in einer Transaktion verändert hat, die man nach dem Fangen der Exception zurückrolllen kann. Man sollte allerdings beachten, dass das rollback nur bereits ausgeführte Datenbankstatements zurückrollt. Die inhaltlichen Änderungen in den Java Objekten (f.setName("Foo")) werden aber NICHT zurückgerollt. Da sie weiterhin mit einer Session verknüpft sind, werden sie mit dem nächsten commit (z.B. im nächsten Schleifendurchlauf) in die Datenbank persisiert!
Deshalb bei einem Rollback IMMER die Hibernate Session schließen (und gegebenenfalls wieder eine neue öffnen):
catch(Exception e)
{
t.rollback();
lSession.close();
lSession=mSessionFetcher.getSession();
}
....
Reihenfolge in der Hibernate SQL Befehle erzeugt
Reihenfolge in der Hibernate SQL Befehle erzeugt
2. all entity updates
3. all collection deletions
4. all collection element deletions, updates and insertions
5. all collection insertions
6. all entity deletions in the same order the corresponding objects were deleted using Session.delete()
Das ist z.B. ein Problem. wenn man erst ein Objekt löschen möchte und dann einen Ersatz dafür einfügen.
lSession.insert(new);
lSession.commit();
Hibernate fügt erst das neue ein und löscht dann das alte. Gibt es einen fachlichen Contraint der doppelte Einträge verhindern soll, bricht der Vorgang ab. Workarround:
lSession.flush();
lSession.insert(new);
lSession.commit();
Debug
Mit folgendem Eintrag in der hibernate.cfg.xml
bekommt man die ausgeführten (Prepared) SQL Statements angezeigt.
Und wenn man das in log4j setzt, sieht man auch die Parameter der Prepared Statements
log4j.logger.org.hibernate.type = TRACE
Der Vorteil der zweiten Variante ist, dass die SQL Statements nicht in die Standardausgabe geschrieben werden, sondern in die log4j Logdatei, zwischen die bereits vorhandenen anderen Logmeldungen.
Migration von Hibernate 2.x nach 3.x
Closed Connection
In einer Schleife wird ab und zu ein commit ausgeführt
Query q=...;
Iterator i=q.iterate();
while (i.hasNext())
{
Object o=i.next();
...
s.update(o);
counter++;
if(counter % MAXOBEJECTSPERCOMMIT)
{
t.commit();
}
}
In Hibernate 2.x war das kein Problem, in Hibernate 3.x ist nach dem commit aber leider die Connection geschlossen.
result - java.sql.SQLException: Closed Resultset: next
Die Ursache liegt im neuen Default connection release mode der in Hibernate 3.x auf AFTER_TRANSACTION gesetzt wurde. Damit wird nach dem Commit die Connection geschlossen. Entweder auf den Connection Release Mode on_close wechseln (wovon aber abgeraten wird) oder eben erst am Ende das Commit ausführen.
SQLite Hibernate
Zurück zur .
Wenn man einen funktionierende JDBC Treiber eingebunden hat, kann man einfach ganz normal Hibernate verwenden. Man benötigt zusätzlich eine Klasse und muss diese in der der referenzieren. Hier wählt man auch die Datei aus, in der die SQLite DB liegen soll. Username und Passwort sind per Default leer.
Das hier sind die jars, die ich für Hibernate eingebunden hatte:
antlr-2.7.6.jar
asm-attrs.jar
asm.jar
cglib-2.1.3.jar
commons-collections-3.2.jar
commons-logging-1.1.1.jar
dom4j-1.6.1.jar
ejb3-persistence.jar
hibernate-annotations.jar
hibernate-commons-annotations.jar
hibernate3.jar
jta.jar
log4j-1.2.14.jar
slf4j-api-1.5.11.jar
slf4j-log4j12-1.5.11.jar
Dann benötigt man natürlich Tabellen / Klassen, die gemappt werden sollen. Beispiele 1: mit Mapping und mit Annotations.
Jetzt noch eine und so liest man dann aus der DB: .
Auf Hibernate-SQLite gibt es ein Maven Projekt und unter Hibernate-SQLite Download befindet sich ein fertiges Beispielsprojekt mit allen Abhängigkeiten (allerdings ohne Annotations Unterstützung).
Da SQLite automatisch eindeutige IDs für neue Zeilen erzeugt, kann man dieses Feature auch in Hibernate nutzen, um eindeutige IDs zu erhalten:
@GeneratedValue(strategy=GenerationType.TABLE)
@Column(name=id, nullable=false, updatable=false, insertable=false)
public Long getId() {
...
}
Hibernate Tools
Mit den Hibernate Tools kann man die Hibernate Mappings für Tabellen in einer Datenbank automatisch erstellen lassen.
Hibernate Tools Links
- Hibernate Tools
- Eclipse Hibernate Tools Installations URL
Hibernate Mappings mit den Hibernate Tools in Eclipse erzeugen lassen
In Eclipse die Hibernate Tools über folgende URL hinzufügen
Danach in Eclipse unter
eine neue Konfiguration hinzufügen
Project ...
Database connection: Hibernate configured connection
Property file: Create new
Property file:
Configuration file: hibernate.cfg.xml:
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
<property name="hibernate.connection.username">MYUSER</property>
<property name="hibernate.connection.password">SUPERSECRET</property>
<property name="hibernate.connection.url">jdbc:oracle:thin:@foo.bar.example.com:1521:mydb</property>
<property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
</session-factory>
</hibernate-configuration>
Dann den Reiter Option auswählen
Jetzt gibt es ein neues Symbol in der Eclipse Leiste
Über das Symbol kann man mit einem Rechtsklick auf
das Verzeichnis festlegen, in das die erzeugten Dateien abgelegt werden.
Um jetzt aus einer Tabelle eine Java Datei zu erzeugen, muss man eine Reverse engineering Datei (hibernate.reveng.xml) anlegen:
<!DOCTYPE hibernate-reverse-engineering PUBLIC "-//Hibernate/Hibernate Reverse Engineering DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-reverse-engineering-3.0.dtd" >
<hibernate-reverse-engineering>
<table-filter match-schema="foo" match-name="bar"/>
</hibernate-reverse-engineering>
In der Datei bennent man die entsprechende Tabelle. Danach kann man über den Reiter
die Option
Domain code (.java)
auswählen, sicherstellen dass unter dem Reiter
die Reverse engineering Datei ausgewählt ist. Will man aus einer anderen Tabelle Java Code erzeugen einfach die Datei entsprechend verändern.