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/MyClass.hbm.xml"/>
<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:

<class name="de.tgunkel.de.hibernate3" table="MeineFarben">
<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.

Session session = ManagerHibernateSession.currentSession();

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.

ManagerHibernateSession.hsqlCleanup(session);

Diese kann dann auch noch die Hibernate Session schließen

ManagerHibernateSession.closeSession();

Dazwischen kann man jetzt Hibernate benutzen um Daten in eine Datenbank zu sichern:

Farbe rot=new Farbe(255, 0, 0);
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:

// Transaction to read from the db via Hibernation
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

catch (HibernateException e)

Dabei ist Farbe kein Tabellenname sondern ein Klassenname der ein Hibernatemapping auf eine Tabelle hat.

Hibernate Objekte können folgende Zustände haben:

persistenttransientdetached
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

<version column="version" name="version" />

oder über eine Annotation

@Version
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.

@Version
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.

CREATE OR REPLACE TRIGGER foo.tableX_update_trigger
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

Wenn mancascade benutzt und ein Objekt speichert, das aber andere Objekte verweist, werden auch all die Objekte automatisch gespeichert, auf die verwiesen wird. '''Achtung''': Löscht man dieses Objekt, werden dann auch alle Objekte gelöscht, auf die verwiesen wurde. Verwendet man kein cascade, muss man die Objekte alle selbst speichern und zwar die zuerst, auf die in anderen verwiesen wird.

Mögliche Probleme

Kommt es zu folgendem Fehler

org.hibernate.MappingException: Resource: MyOtherClass.hbm.xml not found
  • Gibt es die Datei wirklich?
  • Liegt die Datei dort, wo auch die Klasse liegt?

Steht im Mapping auch der Names des Packages? Also

resource="<b>foo/bar/</b>MyOtherClass.hbm.xml"

one-to-one

Eine Objekt der Klasse Foo hat ein Objekt vom Typ Bar

<class name="Foo" table="foo"
<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.

<class name="Foo" table="foo">
...
<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:

private Set myBars;
public Set getBar();
public void setBar(Set bar);

Im Hibernate Mapping sieht das dann so aus:

<class name="Foo" table="foo">
<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.

public void addBar(Bar b)
{
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_OIDVNAME
11Heinz
12Juergen
13Johan

SOHN:

S_OIDSNAMEREF_V_OID
362Paul11
363Lena12
364Sandra11
365Andrea11

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:

SELECT * FROM VATER v left outer join SOHN s on v.V_OID=s.REF_V_OID;

Die entsprechende Java Klassen sehen dann so

@Entity(name="sohn")
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

@Entity(name="vater")
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

s.setVater(v);

(B) Man kann dem Vater einen Sohn hinzufügen

v.getSoehne().add(s);

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

@OneToMany(mappedBy="vater")

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

inverse="true"

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.

inverse="true" cascade="all"

ist vermutlich nicht besonders sinnvoll.

Siehe auch Hibernate inverse mapping.

Mapping Beispiel

Man hat zwei Tabellen

Parent:

idPParentName

Child:

idCChildNameidrefP

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

@Entity
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

@Entity
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

insertable=false, updatable = false erhält man einen Fehler

Vergisst man das erhält man

Repeated column in mapping for entity: (should be mapped with insert="false" update="false")

LazyInitializationException

Früher oder später wird man dieser Exception begegnen

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: FOO, no session or session was closed

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

session.update(Vater);

verbindet Vater wieder mit der Session, dann können auch wieder Kind Objekte bezogen werden.

Hibernate generiert IDs

@Id
@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

while(...)
{
Foo foo=new foo();
foo.setBar(...);
...
session.save(foo);
}
session.commit();

Die ID soll dabei aus einer Datenbank Sequenz gezogen werden

<hibernate-mapping ...>

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

select FOO_OID_SEQ.nextval from dual;

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:

<hibernate-mapping ...>

<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.

@Id
@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

F1F2Data

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.

@Embeddable
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.

@Entity
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:

@Enumerated(EnumType.STRING)
public MyEnumType getStatus() { ... }

Hibernate Query Langugage (HQL)

HQL

Bind parameters

Bind parameters

  • named parameter
Query q = sess.createQuery("from MyFoo foo where foo.name = :name");
q.setString("name", "Bar");
Iterator foos = q.iterate();
  • positional parameter
Query q = sess.createQuery("from MyFoo foo where foo.name = ?");
q.setString(0, "Bar");
Iterator foos = q.iterate();
  • named parameter list
List names = new ArrayList();
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

from C1

oder

from C2

schreiben. Man kann dann auch immer ein ein cartesisches Produkt bilden

from C1, C2

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

from C1 inner join C1.ref

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

from C1 AS a where a.ref.price is not null

So kann man erzwingen, dass alle verknüpften Objekte sofort mitgeladen werden

from Foo fetch all properties where foo.bar=...

Man kann in einer HQL Query auch bestimmen, welche Objekte oder Attribute man zurückgeliefert bekommt

select A,D from A,B,C,D

Objekte löschen

Wenn man über HQL Objekte löschen will, geht das eigentlich so

int deletedEntities = s.createQuery( "delete Foo f where f.id = :myID" ).setString( "myID", 42 ).executeUpdate();

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:
query must begin with SELECT or FROM

Ein Workarround ist dann, die zu löschenden Objekte erst mal mit Hibernet auszulesen und dann mit delete zu entfernen

Query query = mySession.createQuery("select from Foo f where f.id=:myID").setString("myID", 42);
Transaction myTransaction = mySession.beginTransaction();{ @SuppressWarnings("unchecked") List&lt;Foo&gt; lSearchResult= (List&lt;Foo&gt;) query.list(); Iterator&lt;Foo&gt; i=lSearchResult.iterator(); while(i.hasNext()) mORSPropertySession.delete(i.next());}

Oder natürlich einfach SQL statt HQL benutzen.

Materialized View per HQL aktualisieren

SQLQuery lQuery=mSession.createSQLQuery("{call DBMS_MVIEW.REFRESH('foo.bar', 'c')}");
lQuery.executeUpdate();

Hibernate SQL Queries

Hibernate SQL Queries

Man kann über Hibernate auch normal SQL Queries ausführen lassen.

String sqlStrng="SELECT f.id, f.name, f.price, a.id as appleid from fruits f, apples a where a.fruitid=f.id";
SQLQuery sqlQuery=lSession.createSQLQuery(sqlStrng);
sqlQuery.list();

Sofern man nur eine Spalte selektiert hat, erhalt man als Ergebnis

List<Object>

zurück, hat man mehr als eine Spalte selektiert, erhält man

List<Object[]>

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("id", Hibernate.LONG);
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 sqlQuery=lSession.createSQLQuery("select f.* from fruits f");
sqlQuery.addEntity(Fruits.class);

Auch das Auslesen mehrerer Tabellen ist möglich:

SQLQuery sqlQuery=lSession.createSQLQuery("select { f.* }, { a.* } from fruits f, apples a where ...");
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

String sqlStrng="SELECT f.id, f.name, f.price, a.id as appleid from fruits f, apples a where a.fruitid=f.id";
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.flush();
session.clear();

aufrufen und das hier setzten (n durch die tatsächliche Anzahl ersetzten)

hibernate.jdbc.batch_size n

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

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ..., no session or session was closed

Hibernate Criteria Queries

Hibernate Criteria Queries

So kann man zählen wieviel Elemente es hiervon gibt

Criteria crit=s.createCriteria(pClass);
crit.setProjection(Projections.rowCount());
Integer c=(Integer) crit.uniqueResult();

Eine normale where Bedingung

Criteria crit=session.createCriteria(Product.class);
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:

Criteria crit=s.createCriteria(pClass);
ProjectionList projList = Projections.projectionList();
projList.add(Projections.min("id"));
crit.setProjection(projList);

Hibernate Annotations

Man benötigt die entsprechenden jars. Mindestens

hibernate-annotations.jar
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.

sessionFactory = new AnnotationConfiguration().buildSessionFactory();

In der cfg.xml verweist man statt auf eine hbm.xml

<mapping resource="foo/Bar.hbm.xml" />

auf eine Klasse

<mapping class="de.foo.Bar" />

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

@Entity(name="Bar")
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

from de.foo.Bar

statt

from Bar

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

@Transient

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).

@Entity(name="Bar")
@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

Duplicate annotation @NamedQuery Fehlermeldung.

Wenn man mehr als eine Named Query benötigt geht das so;

@Entity(name="Bar")
@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

1. all entity insertions in the same order the corresponding objects were saved using Session.save()
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.delete(old);
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.delete(old);
lSession.flush();
lSession.insert(new);
lSession.commit();

Debug

Mit folgendem Eintrag in der hibernate.cfg.xml

<property name="hibernate.show_sql">true</property>

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.SQL = TRACE
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

Transaction t=beginTransaction();
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.

org.hibernate.exception.GenericJDBCException: could not get next iterator
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:

sqlite-jdbc-3.6.0.jar

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:

@Id
@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 Mappings mit den Hibernate Tools in Eclipse erzeugen lassen

In Eclipse die Hibernate Tools über folgende URL hinzufügen

http://download.jboss.org/jbosstools/updates/JBossTools-3.1.1.GA/

Danach in Eclipse unter

Windows -> Show view -> Other -> Hibernate -> Show Hibernate Configurations

eine neue Konfiguration hinzufügen

Add configuration
Type: Annotations
Project ...
Database connection: Hibernate configured connection
Property file: Create new

Property file:

Eine Datei mit dem Namenhibernate.properties erzeugen und darauf verweisen (kann erst mal leer bleiben).

Configuration file: hibernate.cfg.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!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

Database dialect: Oracle 11G

Jetzt gibt es ein neues Symbol in der Eclipse Leiste

Hibernate tools Eclipse

Über das Symbol kann man mit einem Rechtsklick auf

Hibernate Code Generation and Configurations

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:

<?xml version="1.0" encoding="UTF-8"?>
<!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

Exporters

die Option

Generate EJB3 annotations
Domain code (.java)

auswählen, sicherstellen dass unter dem Reiter

Common

die Reverse engineering Datei ausgewählt ist. Will man aus einer anderen Tabelle Java Code erzeugen einfach die Datei entsprechend verändern.