Für den Anfang ist
hsqldb sehr gut geeignet. Alle Daten werden dabei in einem Ordner namens "data" im Projektordner als Plaintext abgelegt.
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
log4j.properties konfiguriert werden sollte.
Hibernation selbst braucht auch eine Configdatei,
hibernate.cfg.xml. 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
Managerklasse, 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:
| persistent |
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(). |
| transient |
Noch nie persistent gewesen, nicht mit einer Session verknüpft. |
| detached |
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.
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.
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.
Siehe auch
xylax.net: Index of Relationships
Wenn man
cascade
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.
Kommt es zu folgendem Fehler
org.hibernate.MappingException: Resource: MyOtherClass.hbm.xml not found
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)
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.
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.
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:
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.
Man hat zwei Tabellen
Parent:
Child:
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")
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.
@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.
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
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() {
}
}
HQL
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();
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
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:
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<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.
SQLQuery lQuery=mSession.createSQLQuery("{call DBMS_MVIEW.REFRESH('foo.bar', 'c')}");
lQuery.executeUpdate();
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(KaggValueSystemStatusDBResultContainer.class));
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
Hibernate Criteria Queries
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();
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.
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
{
...
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
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();
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.
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.
14-08-2011 23.02