Entwicklung der Softwarearchitektur zum Projekt

Werbung
- Diplomarbeit -
Entwicklung der Softwarearchitektur
zum Projekt SAS III
Implementierung der Datenbankzugriffe im Projekt SAS III unter
Verwendung des OR-Mappers Hibernate
Verfasser:
Johannes Artner
Reitern 8
A-3672 Maria Taferl
Gutachter:
Mag. Otto Reichel
Bearbeitungszeitraum: September 2009 - Mai 2010
Erklärung
Ich versichere hiermit, dass ich die vorliegende Diplomarbeit mit dem
Thema Implementierung der Datenbankzugriffe im Projekt SAS III
”
unter Verwendung des OR-Mappers Hibernate“ selbstständig verfasst
und keine anderen als die angegebenen Hilfsmittel benutzt habe.
i
Danksagungen
An dieser Stelle möchte ich mich bei allen Personen bedanken, die
mich bei der Erstellung dieser Diplomarbeit unterstützt haben.
Ein großer Dank gilt meinen Eltern, die es mir ermöglichten, diese
Schulausbildung zu erhalten und mich dabei so gut es ging
unterstützten.
Bedanken möchte ich mich auch bei meinem Betreuungslehrer, Herrn
Prof. Mag. Otto Reichel, sowie dem gesamten Projektteam für die gute
Zusammenarbeit.
ii
Zusammenfassung
Projekt SAS III
In diesem Projekt wird die serverseitige Software SAS
(Schuladministrations Software) auf Web 2.0 umgestellt, um somit eine
dynamische Java-Webapplikation zu entwickeln. Um dieses Ziel zu
erreichen, werden die Technologien ICEFaces, Facelets und Hibernate
evaluiert und verwendet.
Übersicht über die Diplomarbeit:
Ziel der Diplomarbeit ist es, die Anbindung an die Datenbank im Projekt
SAS III unter der Verwendung des OR-Mappers Hibernate praktisch
umzusetzen. Die verwendete Technologie wird unter Berücksichtigung
der Einsatzmöglichkeiten in SAS III neben der praktischen Umsetzung
auch theoretisch analysiert.
iii
Abstract
Project SAS III
The aim of this project is to convert the server-application SAS (School
administration Software) to Web 2.0, to get a dynamic Java web
application. To achieve this goal, the technologies ICEfaces, Facelets
and Hibernate are evaluated and used.
Overview on the diploma thesis:
The aim of this dissertation is to maintain the access to the database
using the OR-Mapper Hibernate. The used technology is also analyzed
theoretically, considering to the field of application of SAS III.
iv
Inhaltsverzeichnis
Vorwort
i
Erklärung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
i
Danksagungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
ii
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
iii
Abstract . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
iv
Inhaltsverzeichnis
v
1 Motivation
1
1.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2 Objekt-Relationaler-Bruch . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
1.2.2 Granularität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2.3 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.2.4 Identität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.2.5 Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
1.2.6 Kardinalitäten . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
1.2.7 Datennavigation . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
v
1.3 Lösungsansätze . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2 Hibernate
5
7
2.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
2.2 Einsatzgründe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
2.3 Java Persistence API (JPA) . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.4 Komponenten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.4.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.4.2 Hibernate Core . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
2.4.3 Hibernate Annotations . . . . . . . . . . . . . . . . . . . . . . . .
10
2.4.4 Hibernate EntityManager . . . . . . . . . . . . . . . . . . . . . .
10
2.4.5 Hibernate Shards
. . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.4.6 Hibernate Validator . . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.4.7 Hibernate Search
. . . . . . . . . . . . . . . . . . . . . . . . . .
11
2.4.8 Hibernate Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
3 Technologiebeschreibung Hibernate
13
3.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
3.2 OR-Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
3.2.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
3.2.2 Mapping mit XML . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
3.2.3 Mapping mit Annotations . . . . . . . . . . . . . . . . . . . . . .
20
3.2.4 Mapping mit XDoclet . . . . . . . . . . . . . . . . . . . . . . . . .
21
3.3 Entitäten/Werttypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
3.4 Component Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
vi
3.5 Identität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
3.5.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
3.5.2 Synthetischer Primärschlüssel . . . . . . . . . . . . . . . . . . .
24
3.5.3 Fachlicher Primärschlüssel . . . . . . . . . . . . . . . . . . . . .
26
3.6 Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
3.7 Beziehungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
3.7.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
3.7.2 Kardinalitäten . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
3.7.3 many-to-one / one-to-many . . . . . . . . . . . . . . . . . . . . .
36
3.7.4 one-to-one . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
3.7.5 many-to-many . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
3.8 Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
3.8.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
3.8.2 Tabelle pro Klassenhierarchie (SINGLE TABLE) . . . . . . . . .
42
3.8.3 Tabelle pro Subklasse (JOINED) . . . . . . . . . . . . . . . . . .
44
3.8.4 Tabelle pro konkreter Klasse (TABLE PER CLASS) . . . . . . .
46
3.9 Konfiguration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
3.9.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
3.9.2 hibernate.properties . . . . . . . . . . . . . . . . . . . . . . . . .
48
3.9.3 hibernate.cfg.xml . . . . . . . . . . . . . . . . . . . . . . . . . . .
49
3.9.4 Zusätzliche Methoden . . . . . . . . . . . . . . . . . . . . . . . .
50
3.9.5 Konfiguration beim Mapping mit Annotations . . . . . . . . . . .
51
3.10 Sessions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
vii
3.10.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
3.10.2 Session-Level-Cache . . . . . . . . . . . . . . . . . . . . . . . .
54
3.10.3 Lebenszyklus von Entitäten . . . . . . . . . . . . . . . . . . . . .
54
3.11 Connections
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
3.11.1 Connection-Pool . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
3.11.2 JNDI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
3.12 Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
3.12.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
3.12.2 Konkurrierende Datenbankzugriffe . . . . . . . . . . . . . . . . .
59
3.12.3 Transaktionsisolation . . . . . . . . . . . . . . . . . . . . . . . . .
61
3.12.4 Sperrstrategien . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
3.13 Datenbankabfragen . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
65
3.13.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
65
3.13.2 Hibernate Qery Language (HQL) . . . . . . . . . . . . . . . . . .
66
3.13.3 Criteria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
3.13.4 SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
3.14 Fetching und Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
3.14.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
75
3.14.2 Lazy Loading . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
76
3.14.3 Eager Loading . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
3.14.4 Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
3.15 Entwicklungsstrategien . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
3.15.1 Top down . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
viii
3.15.2 Bottom up . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
3.15.3 Middle-Out . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
81
3.15.4 Meet-in-the-Middle . . . . . . . . . . . . . . . . . . . . . . . . . .
81
4 Pattern
83
4.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83
4.2 Session per Request . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
84
4.3 Session per Conversation . . . . . . . . . . . . . . . . . . . . . . . . . .
85
4.4 Open Session in View . . . . . . . . . . . . . . . . . . . . . . . . . . . .
87
5 Umsetzung im Projekt SAS III
89
5.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
89
5.2 Aufbau der Applikation . . . . . . . . . . . . . . . . . . . . . . . . . . . .
90
5.2.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
90
5.2.2 POJO und Mapping-Informationen . . . . . . . . . . . . . . . . .
90
5.2.3 Konfiguration . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
91
5.2.4 SessionFactory . . . . . . . . . . . . . . . . . . . . . . . . . . . .
92
5.3 Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
94
5.3.1 Übersicht . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
94
5.3.2 Navigationsbäume . . . . . . . . . . . . . . . . . . . . . . . . . .
96
5.3.3 Mapping Generator . . . . . . . . . . . . . . . . . . . . . . . . .
98
5.4 Sessions, Transaktionen und Lazy Loading . . . . . . . . . . . . . . . . 107
5.5 Sperren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.6 Component-Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.7 Criteria . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
ix
5.8 Identität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
6 Konklusion/Ausblick
115
Anhang
117
Abbildungsverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
Tabellenverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Verzeichnis der Listings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Literaturverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
x
Kapitel 1
Motivation
1.1 Übersicht
Beinahe jede Anwendung benötigt persistente Daten. Diese Daten werden so gut wie
immer in relationalen Datenbanken verwaltet.
Das relationale Datenbankmodell wurde 1970 entwickelt und ist heute der de facto
Standard. Dieses Konzept findet in kommerziellen, wie auch open-source Implementierungen Verwendung. Es ist flexibel, robust und über viele Jahre erprobt.
Zur Verwaltung solcher Datenbanken dienen RDBMS1 .
Mithilfe von SQL2 -Statements können Daten manipuliert bzw. ausgelesen werden.
Mit der Entwicklung objektorientierter Programmiersprachen fand ein Bruch zwischen
Anwendung und relationaler Datenbank statt. Dieser Strukturbruch wird als ObjektRelationaler-Bruch bezeichnet.
1.2
Objekt-Relationaler-Bruch
1.2.1 Übersicht
Bei der Abbildung von Objekten auf relationale Datenbanken stehen sich zwei grundsätzlich unterschiedliche Modelle gegenüber.
1
2
Relationale Datenbank Management Systeme
Structured Query Language
1.2 Objekt-Relationaler-Bruch
2
Objektorientierte Programmiersprachen verfügen über ein hierarchisches System von
Objekten und Beziehungen zueinander.
Relationale Datenbanken hingegen besitzen eine flache Struktur von Tabellen und Beziehungen.
Der Objekt-Relationale-Bruch tritt erst bei komplexen Objektstrukturen auf. Ein einfaches Objekt kann als Zeile einer Tabelle gesehen werden.
Die wesentlichen Unterschiede betreffen:
• Granularität
• Vererbung
• Identität
• Datentypen
• Kardinalitäten
• Datennavigation
1.2.2
Granularität
Der Begriff Granularität bezieht sich auf die Anzahl der Unterscheidungsstufen von
Elementen.
In diesem Zusammenhang betrifft dies die Möglichkeit, eigene Datentypen zu verwenden.
Java ist fein granular.
Klassen bzw. Objekte werden als Datentypen betrachtet. Die Eigenschaften dieses
Datentyps werden durch Instanzvariablen realisiert. Es können beliebige Datentypen
angelegt werden. Hierbei nimmt auch die has-a“-Beziehung eine wesentliche Rolle
”
ein. Objekte können Referenzen auf andere Objekte beinhalten, welche wiederum andere Objekte kapseln, ... .
Relationale Datenbanken hingegen sind grob granular.
Zwar gibt es in einigen Datenbanksystemen die Möglichkeit, eigene Datentypen zu
realisieren, jedoch wird dies aufgrund von Kompatibilitätsproblemen und häufig auftretenden Fehlern abgelehnt. Eine Tabelle ist kein Datentyp.
1.2 Objekt-Relationaler-Bruch
1.2.3
3
Vererbung
Ein wesentliches Element jeder objektorientierten Programmiersprache ist die Vererbung.
Subklassen erben die Eigenschaften der Superklassen. Prinzipiell kann dieser Umstand mittels Sichten, sogenannten Views, auf Datenbankseite gelöst werden.
Doch objektorientierte Programmiersprachen bieten ein weiteres Feature: Den Polymorphismus.
Erst zur Laufzeit wird ersichtlich, welcher Objekttyp hinter einer Referenz steckt. Der
Laufzeittyp muss zwar in der Vererbungslinie liegen, jedoch kann zur Compilezeit keine Aussage über den konkreten Typ getroffen werden. Auf Datenbankseite muss dies
jedoch feststehen. Schließlich referenziert ein Foreign-Key einen Datensatz einer bestimmten Tabelle.
1.2.4
Identität
Objekte können nach zwei Kriterien verglichen werden:
• Objektgleichheit
Beide Referenzen verweisen auf dasselbe Objekt. Dies wird durch den == Operator geprüft.
• Wertgleichheit
Zwei Objekte kapseln die gleichen Werte. D.h. Die Instanzen der Objekte haben
den gleichen Inhalt. Dies wird durch die Methode equals() geprüft.
Auf Datenbankseite wird die Identität mittels Primärschlüssel (engl. Primary Key) realisiert. Dieser ist innerhalb einer Tabelle eindeutig. Weder der == Operator, noch die
Methode equals() entsprechen diesem Konzept.
1.2.5
Datentypen
Java und Datenbanken stellen unterschiedliche Datentypen bereit. Grundsätzlich wird
dieses Problem bereits von der JDBC3 -Schnittstelle gelöst. Datentypen der Datenbank
werden in sinnvolle Java-Typen konvertiert (z.B. String - VARCHAR).
3
Java Database Connectivity
1.2 Objekt-Relationaler-Bruch
4
Jedoch bleibt ein gewisser Restaufwand. Auf Datenbankseite gibt es Zeichenbeschränkungen. Aus Performancegründen ist es sinnvoll, diesen Datenbankmechanismus auch
auszunützen. Ansonsten würde beispielsweise der Java Datentyp String umgelegt
auf die Datenbank die maximale Zeichenanzahl eines Varchars belegen.
1.2.6
Kardinalitäten
In Java werden Beziehungen mittels Objektreferenzen dargestellt.
Eine Referenz stellt eine Beziehung in genau einer Richtung dar. Sie ist unidirektional.
Will man, dass eine Beziehung in beiden Richtungen besteht, muss auf beiden Seiten
eine Objektreferenz angelegt werden.
Auf Datenbankseite werden Fremdschlüssel (engl. Foreign Keys) verwendet.
Beziehungen bestehen in beiden Richtungen. Sie sind bidirektional.
Bei einer m:n-Beziehung wird in relationalen Datenbanken eine Kreuztabelle angelegt.
Im Domainenmodell von Java existiert diese Kreuztabelle nicht. Mehrwertige Beziehungen werden mithilfe von Collections implementiert.
1
2
3
4
5
6
7
public class Schueler{
private Set<Erziehungsberechtigter> erziehungsberechtigte;
}
public class Erziehungsberechtigter{
private Set<Schueler> kinder;
}
Listing 1.1: m:n-Beziehung in Java
1.2.7
Datennavigation
Ein weiterer Effekt der unterschiedlichen Struktur betrifft die Datennavigation.
Will man in Java auf ein bestimmtes Element zugreifen, iteriert man das Objektnetzwerk bis zum gewünschten Element durch.
aktDirektion.getOrt().getOrtPlz();
1.3 Lösungsansätze
5
Auf Datenbankseite muss man entweder Joins oder Subselects verwenden, um die
Postleitzahl der Direktion zu bekommen.
SELECT ort.ort_plz
FROM Direktion AS dir
JOIN Ort AS ort
ON ort.ort_id = dir.dir_ort_id
WHERE dir.dir_schulkennzahl = ’302467’;
1.3
Lösungsansätze
Es gibt nachstehende Möglichkeiten, um den Problemen des Objekt-RelationalenBruchs zu begegnen.
• Die ursprüngliche Form ist, SQL-Statements selbst zu formulieren und so den
Objekt-Relationalen-Bruch händisch“ zu umgehen. Ergebnisse von Anfragen müssen
”
über Resultsets in die objektorientierte Struktur übertragen werden.
Hierbei geht jedoch viel Zeit verloren und das Risiko des Scheiterns eines Softwareprojektes steigt.
• Ein weiterer Ansatz ist, objektorientierte Datenbanken zu verwenden.
Objektrelationale Datenbanken, wie Postgres oder Oracle, erweitern das relationale Modell um Konzepte objektorientierter Programmiersprachen.
Diese Datenbankkonzepte finden derzeit jedoch noch wenig Verwendung. Aktuell
bergen sie Probleme und sind nicht ausgereift. Des Weiteren lösen diese Datenbanken den Objekt-Relationalen-Bruch nicht vollständig.
• Die dritte Möglichkeit ist die Verwendung eines OR-Mappers4 . Ein OR-Mapper
stellt eine zusätzliche Schicht zwischen Anwendung und Datenbank dar.
Neben dem Umgehen des Objekt-Relationalen-Bruchs gibt es weitere Gründe für den
Einsatz eines OR-Mappers:
4
Objekt-Relationaler-Mapper
1.3 Lösungsansätze
6
• Performance
Gute OR-Mapper optimieren die SQL-Abfragen hinsichtlich Performance.
• Weniger Code
SQL-Statements werden vom OR-Mapper erzeugt und müssen daher nicht mehr
händisch“ in den Quellcode geschrieben werden.
”
Entwickler/innen müssen sich nicht darum kümmern, wie die Daten aus der Datenbank geladen bzw. gespeichert werden.
Dabei spricht man von der Transparenten Persistenz.
Programmierer/innen können sich auf die Applikationslogik konzentrieren. Dadurch wird die Entwicklung effizienter.
• Wartbarkeit
Ein weiterer Effekt des reduzierten Codes ist, dass sich neue Entwickler/innen
schneller in ein Projekt einarbeiten können.
• Datenbankabstraktion
Die SQL-Statements werden zur Laufzeit je nach Datenbankdialekt formuliert.
Dadurch ist die Applikation einfach auf andere Datenbanken portierbar.
Die Abhängigkeit zu einer Datenbankimplementierung sinkt, was sich positiv auf
das Projektrisiko auswirkt.
Eine OR-Mapper-Implementierung ist Hibernate.
Kapitel 2
Hibernate
2.1 Übersicht
Vor allem in der Java-Welt hat sich Hibernate als Standard OR-Mapper etabliert.
Für das .NET Framework gibt es die äuquivalente Technologie NHibernate.
Hibernate ist Bestandteil der JBoss-Gruppe, welche wiederum zu RedHat gehört. Die
erste Hibernate-Version wurde im Jahr 2002 veröffentlicht.
Die Hibernate-Bibliotheken stehen auf www.sourceforge.net, der weltgrößten OpenSource-Webseite zur Verfügung. Dort zählt das Hibernate-Projekt weit über 5 Millionen
Downloads.
2.2
Einsatzgründe
Gründe für den Einsatz von Hibernate sind:
• Open Source
– Es kann kostenfrei genutzt werden.
Jedoch bleibt zu beachten, dass Schulungen bzw. Weiterbildungen sehr
wohl hohe Kosten verursachen können.
– Der Quelltext kann eingesehen werden.
Dadurch ist es möglich, Detailfragen zu klären, wenn die Dokumentation
2.3 Java Persistence API (JPA)
8
nicht ausreicht. Unternehmen, welche langfristig auf diesen OR-Mapper setzen, können die Weiterentwicklung auch selbst übernehmen, sollte diese
eingestellt werden.
• Hibernate ist weit verbreitet
Dadurch ist es einfach, qualifiziertes Personal zu finden bzw. zu ersetzen. Des
Weiteren gibt es im Internet eine große Community, welche sich Problemen und
Fragen annimmt.
• Hibernate bewährt sich seit Jahren in unzähligen Projekten
Dadurch ist es gut getestet und das Risiko ist sehr gering, dass es für das eigene
Projekt nicht geeignet ist.
• Einfaches Programmiermodell
Die Einarbeitungszeit ist gering.
• Transparenz
Anders als bei anderen OR-Mappern werden Datenbankstrukturen und die Verwendung von SQL nicht vom Anwendungsentwickler verborgen. Dadurch können
Fehler rasch gefunden bzw. gelöst werden.
• Flexibel
Hibernate kann sowohl bei bestehender Datenbank, dem Reverse Engineering,
wie auch zum Generieren eines Datenbankschemas, dem Forward Engineering,
eingesetzt werden.
2.3
Java Persistence API (JPA)
JPA ist Teil der EJB1 3.0 Spezifikation. EJB beschreibt die serverseitige Architektur von
Komponenten in Java EE2 .
JPA definiert die Objekt-Relationale Abbildung.
Sie besteht aus folgenden Komponenten:
• Persistenzobjekt
Hierbei handelt es ich um persistierbare Klassen, welche auf Tabellen abgebildet
werden können.
1
2
Enterprise JavaBeans
Java Enterprise Edition
2.4 Komponenten
9
• Metadaten
Es handelt sich um Informationen der Abbildung von persitierbaren Klassen auf
die Datenbank und umgekehrt. Diese Informationen werden mittels Annotations
in die Persistenzklasse integriert.
• JPQL
Eine standardisierte objekorientierte Abfragesprache, deren Syntax an SQL angelehnt ist.
Die EJB 3.0 Spezifikation wurde 2003 eingeführt. Wichtige Hersteller von OR-Mappern,
darunter auch Hibernate, nahmen Einfluss auf diesen neuen Standard. Besonderer
Wert wurde auf die Portabilität von Code gelegt.
2.4
Komponenten
2.4.1 Übersicht
Hibernate Core stellt die Kernfunktionalität zur Verfügung. Auf dieser Plattform setzen
nachstehende Erweiterungen auf:
• Annotations
• EntityManager
• Shards
• Validator
• Search
• Tools
2.4.2
Hibernate Core
Im Wesentlichen liefert der Hibernate Core folgende Features:
• SQL-Code-Generation
Hibernate generiert die SQL-Statements je nach SQL-Dialekt zur Laufzeit. Als
2.4 Komponenten
10
SoftwareentwicklerIn greift man auf Objekte zu und läd diese durch vordefinierte
Zugriffsmethoden (z.B. save, update, ..) aus der Datenbank.
• Objektorientierung
Unterstützt die Objekt-Orientierten-Konzepte und das Collections-API.
• Objekt-Orientiertes-HQL3 / Einfaches SQL
Sollte es nötig sein, können SQL-Befehle auch selbst formuliert werden. Hierbei
ist es möglich, die Statements im jeweiligen SQL-Dialekt oder mittels HQL zu
verfassen.
• Metadaten in XML-Dateien
Die benötigten Informationen über die Abbildung der Entitäten auf die Datenbank
und umgekehrt stehen in XML-Dateien.
2.4.3
Hibernate Annotations
Jeder OR-Mapper, und so auch Hibernate, benötigt Metadaten.
Früher war es nur möglich, diese Informationen mittels XML-Dateien anzugeben.
Seit dem JDK4 5.0 gibt es die Möglichkeit, Metadaten im Java-Quelltext mittels Annotations einzubinden. Dadurch steht die gesamte notwendige Information in einer Datei.
Hibernate verwendet JPA-Annotations, stellt aber auch eigene bereit, um alle Feinheiten des Mappings zu ermöglichen.
Ab der Hibernate-Version 3.5 sind Hibernate Annotations Teil des Hibernate Core.
2.4.4
Hibernate EntityManager
Diese Komponente umhüllt den Hibernate Core, um vollständige Kompatibilität mit der
JPA-Spezifikation zu gewährleisten.
Insbesondere bedeutet dies die Verwendung von JPQL und der Einsatz von JPAAnnotations.
Ab der Hibernate-Version 3.5 ist der Hibernate EntityManager Teil des Hibernate Core.
3
4
Hibernate Query Language
Java Development Kit
2.4 Komponenten
2.4.5
11
Hibernate Shards
Manchmal ist es notwendig, Datensätze einer Tabelle an unterschiedlichen Orten zu
speichern. Gründe dafür sind vor allem Datensicherheit und Zugriffszeiten.
Die Entwicklung von Software wird dadurch komplex.
Hibernate Shards vereinfacht die Implementierung einer Software-Lösung bei Verwendung von verteilten Datenbanksystemen.
2.4.6
Hibernate Validator
In den Metadaten können Integritätsbestimmungen angegeben werden.
Somit können Eingaben des Benutzers vor dem Speichervorgang geprüft werden.
Beim Forward-Engineering von Hibernate ist es möglich, diese Integritätsregeln in das
Datenbankschema zu integrieren.
2.4.7
Hibernate Search
Häufig ist es notwendig, eine Suchfunktion bereitzustellen.
Das Suchen in normalen Textdateien stellt keine große Herausforderung dar. Schwierig wird es jedoch, wenn nach Daten gesucht wird, welche in relationalen Datenbanken
stehen.
Die in der Java-Welt am weitesten verbreitete Bibliothek für Volltextsuche liefert die
Apache Software Foundation mit Lucene. Lucene ist stabil, weit verbreitet und sehr
gut dokumentiert. Beispielsweise Wikipedia verwendet Lucene zur Volltextsuche.
Hibernate Search verwendet intern Lucene.
In den Hibernate-Metadaten kann ein Index für die Suche erstellt werden.
2.4.8
Hibernate Tools
Hierbei handelt es sich um Plugins für Entwicklungsumgebungen und Apache Ant5
Tasks.
Angeboten werden Unterstützungstools zur Entwicklung von Hibernate-Software. Dies
5
Apache Ant (engl. Ameise) ist neben anderen Verwendungsmöglichkeiten, ein Werkzeug zur automatisierten Erstellung von Quelltexten.
2.4 Komponenten
12
betrifft Code-Vervollständigung, das Reverse-/Forward-Engineering und viele mehr.
Die Entwicklungsumgebung Netbeans 6.7.1, in dem das Projekt SAS III implementiert
wird, stellt diese Hilfen eingeschränkt ebenfalls zur Verfügung.
Kapitel 3
Technologiebeschreibung Hibernate
3.1 Übersicht
Hibernate bildet eine zusätzliche Schicht zwischen Anwendung und Datenbank und
legt die Objektstrukturen auf die relationale Datenbank um.
Hibernate verwendet die in Abbildung 3.1 ersichtlichen Komponenten:
• Entitäten/POJO1 s
Persistierbare Geschäftsobjekte.
• Mappings
Mapping Informationen können in Form von Annotations und XDoclet integriert,
oder in externen XML2 -Dateien stehen.
• Configuration
Hiermit wird das Verhalten von Hibernate und die Datenbankeinstellungen konfiguriert.
• SessionFactory
Wird durch ein Configuration-Objekt erzeugt und liefert Objekte vom Typ
Session. Normalerweise gibt es genau eine SessionFactory pro Applikation.
• Session
Mithilfe von Sessions wird auf die Datenbank zugegriffen.
1
2
Plain Old Java Object
Extensible Markup Language
3.1 Übersicht
14
– Transaction
Um Datenbankbestände zu verändern dienen Transaktionen.´
– Query
Hiermit können Datenbankabfragen formuliert werden.
Abbildung 3.1: Hibernate Ueberblick
Zur Illustrierung wird für die folgenden Kapitel ein einfaches Beispiel aus dem Projekt
SAS III verwendet: Der Gegenstand.
Die Tabelle gegenstand enthält folgende Spalten:
3.2 OR-Mapping
15
• geg_id
Primärschlüssel, serial
• geg_kurzbez
Gegenstandskurzbezeichnung, character varying(10), NOT NULL
• geg_upiskurzbez
Gegenstandskurzbezeichnung in UPIS, character varying(10)
• geg_langbez
Gegenstandslangbezeichnung, character varying(100), NOT NULL
• geg_gar_kurzbez
Gegenstandsartkurzbezeichnung, character(1), NOT NULL, Foreign-Key auf die
Tabelle Gegenstandsart
• geg_notenattr_art
Art des verwendeten Notenattributes, integer
• geg_msp_id
Sprache in der der Gegenstand unterrichtet wird, integer, NOT NULL, ForeignKey auf die Tabelle Muttersprache
• geg_fremdsprbilddok
Kürzel für Fremdsprache aus Bildungsdokumentation, integer
3.2
OR-Mapping
3.2.1 Übersicht
Um die Objekt-Relationale-Abbildung zu beschreiben, gibt es folgende drei Möglichkeiten.
• Mapping mit XML
• Mapping mit Annotations
• Mapping mit XDoclet
Welche Variante des Mappings man verwendet, ist grundsätzlich egal. Die drei Varianten können auch parallel zueinander verwendet werden.
3.2 OR-Mapping
3.2.2
16
Mapping mit XML
Übersicht
Mapping mit Hilfe von XML-Dateien ist die ursprüngliche Form von Hibernate, die
Objekt-Relationale-Abbildung zu beschreiben.
Hierbei stehen Mapping-Informationen und Geschäftsklasse in unterschiedlichen Dateien. Das ist die Persistenzklasse (POJO) und jeweils eine zusätzliche XML-MappingDatei, in denen die Metadaten zur Persistenzklasse stehen.
Abbildung 3.2: POJO - Mapping
POJO
Ein POJO stellt eine Entität der Applikationslogik dar.
Man spricht von einem persistenten Objekt. Der Zustand eines POJOs muss jedoch
nicht persistent sein. Weitere Informationen dazu gibt es im Kapitel 3.10.3.
In der Variante des Mappings mithilfe von zusätzlichen XML-Dateien beinhalten die
POJO-Klassen in der einfachsten Form lediglich die Instanzvariablen, entsprechende
Getter- und Setter Methoden und einen parameterlosen Konstruktor.
Fest vorgeschrieben ist nur der parameterlose Konstruktor.
Es ist konfigurierbar, ob Hibernate direkt, oder mittels Getter/Setter auf die Instanzvariablen zugreift. Standardmäßig werden die Getter/Setter verwendet.
3.2 OR-Mapping
17
1 public class Gegenstand{
2
3
private int gegId;
4
private String gegKurzbez;
5
private String gegUpiskurzbez;
6
private String gegLangbez;
7
private char gegGarKurzbez;
8
private Integer gegNotenattrArt;
9
private int gegMspId;
10
private Integer gegFremdsprbilddok;
11
12
public Gegenstand() {
13
14
}
15
16
public int getGegId() {
17
return this.gegId;
18
}
19
20
public void setGegId(int gegId) {
21
this.gegId = gegId;
22
}
23
//...
24
//Weitere Getter und Setter
25
//...
26 }
Listing 3.1: Klasse Gegenstand
Werden Getter/Setter verwendet, müssen diese dem JavaBean-Standard folgen.
Grundsätzlich ist es nicht erforderlich, Getter und Setter bzw. Konstruktoren öffentlich
zu machen. Hibernate greift mittels Reflection darauf zu.
Mapping-Datei
Der Inhalt dieser Datei beschreibt die Abbildung der einzelnen Klassen (POJOs) auf
die Datenbank und umgekehrt.
Speziell geht es hierbei um die Zuordnung der einzelnen Attribute der POJOs zu den
Spalten der Tabelle.
Es ist zwar möglich, den Inhalt der Mapping-Datei auf ein Minumum zu beschränken.
Sinnvoll ist es jedoch, viele Informationen in die Mapping-Datei zu integrieren. Sonst
leidet die Performance der Applikation. Schließlich müssen dann die genauen Infor-
3.2 OR-Mapping
18
mationen zu den einzelnen Member der Klasse zur Laufzeit geholt werden.
Grundsätzlich ist es möglich, mehr als eine Klasse pro Mapping-Datei zu beschreiben.
Aufgrund der Übersichtlichkeit ist von dieser Vorgangsweise jedoch abzuraten.
In der Java-Welt haben sich zwei Standards etabliert. Zum einen liegen die MappingDateien im selben Paket wie das POJO. Zum anderen entspricht der Name der MappingDatei dem der Klasse und besitzt zusätzlich die Endung .hbm..
In diesem Fall ist der vollständige Dateiname deshalb Gegenstand.hbm.xml..
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping ←DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate- ←mapping-3.0.dtd">
3
4 <hibernate-mapping>
5
<class name="Gegenstand" table="gegenstand" schema="public">
6
<id name="gegId" type="int">
7
<column name="geg_id"/>
8
<generator class="assigned"/>
9
</id>
10
<property name="gegKurzbez" type="string">
11
<column length="10" name="geg_kurzbez" not-null="true" ←unique="true" />
12
</property>
13
<property name="gegUpiskurzbez" type="string">
14
<column length="10" name="geg_upiskurzbez">
15
</column>
16
</property>
17
<property name="gegLangbez" type="string">
18
<column length="100" name="geg_langbez" not-null="true" />
19
</property>
20
<property name="gegGarKurzbez" type="char">
21
<column length="1" name="geg_gar_kurzbez" not-null="true"/>
22
</property>
23
<property name="gegNotenattrArt" type="java.lang.Integer">
24
<column name="geg_notenattr_art" />
25
</property>
26
<property name="gegMspId" type="int">
27
<column name="geg_msp_id" not-null="true" />
28
</property>
29
<property name="gegFremdsprbilddok" type="java.lang.Integer">
30
<column name="geg_fremdsprbilddok" />
3.2 OR-Mapping
19
31
</property>
32
</class>
33 </hibernate-mapping>
Listing 3.2: Gegenstand Mapping File
Wie in obigem Listing zu sehen, besteht eine XML-Mapping-Datei, unter anderen, aus
folgenden Komponenten:
• DOCLET
Am Anfang der XML-Datei wird die DTD3 angegeben. Eine DTD beschreibt die
Struktur eines XML-Dokuments. Hibernate sucht diese Struktur zunächst im Classpath. Wird diese nicht gefunden, wird versucht, sie über den mittels des
<!DOCTYPE>-Tags angegeben Link zu finden.
• hibernate-mapping
Die Mapping-Informationen beginnen mit diesem Element. Grundsätzlich ist es
möglich, mehr als eine Klasse darin zu beschreiben. Doch wie oben erwähnt, ist
davon abzuraten.
Im <hibernate-mapping>-Tag können Standardeinstellungen von Hibernate
geändert werden. Diese Einstellungen gelten für alle Kindelemente, sofern sie
darin nicht überschrieben werden.
• class
Dieses Element ordnet die Klasse einer Tabelle zu.
Der name-Parameter gibt den Namen der Klasse an und der table-Parameter
definiert die verwendete Tabelle.
Der schema-Parameter ist optional. Sollte es in der Datenbank mehrere Schemata geben, könnte man hier den Namen angeben. Wird dieses Attribut weggelassen, wird die Tabelle in der gesamte Datenbank gesucht.
• id
Dieser Tag beschreibt die Identität der Entität. Eine Entität muss eine Identität
besitzen.
Der name-Parameter gibt hierbei den Namen der Instanzvariable und type den
Datentyp im POJO an.
Der <id>-Tag hat folgende Kindelemente:
– column
Beschreibung der Datenbankspalte.
– generator
Dieses Element regelt, wie der Primärschlüssel vergeben werden soll.
In unserem Beispiel erfolgt die Vergabe nach der Strategie assigned.
3
Document Type Definition
3.2 OR-Mapping
20
Mehr Informationen dazu folgen im Kapitel 3.5.
• property und column
Diese Elemente ordnen die Member von Klassen den Tabellen und umgekehrt
zu.
Auch hier gilt wieder: Umso mehr Informationen, desto schneller kann Hibernate
arbeiten. Aus diesem Grund sollten auch optionale Parameter, wie beispielsweise
type, angegeben werden.
Hier können auch Datenintegritätsregeln angegeben werden. In diesem Beispiel
werden die Parameter not-null und length gesetzt.
3.2.3
Mapping mit Annotations
Seit Java 5 gibt es die Möglichkeit, Metadaten direkt im Quellcode bereitzustellen. Dies
ist mithilfe von Annotations möglich.
Hibernate erschien 2002. Java 5 im Jahr 2004. Dadurch kam dieses Feature erst
später hinzu. Vor diesem Zeitpunkt war das Hinterlegen von Metadaten für Hibernate
hauptsächlich in seperaten XML-Dateien vorgesehen.
Mapping mit Annotations hat den Vorteil, dass die Informationen zu einer Klasse zentral in der Klassendatei abgelegt werden. Somit ist die Zuordnung von Instanzvariablen
zu Spalten und umgekehrt einfacher.
Alle Konzepte des XML-Mappings können auch beim Mapping mit Annotations angewendet werden.
In dieser Diplomarbeit wird vor allem auf das Mapping mit XML eingegangen. Es werden aber immer wieder Ausblicke auf das Mapping mit Annotations gegeben.
Annotations
Eine Annotation (engl. Vermerk, Kommentar) beginnt mit einem @. Danach wird der
Name der Annotation angegeben. Parameter werden nach dem Namen in runden
Klammern angegeben (z.B. @Table(name = "tabelle1")).
Hat eine Annotation nur einen Parameter, so muss der Name nicht angegeben werden. Es reicht, einfach den Wert anzugeben. Bei parameterlosen Annotations ist das
Setzen von Klammern optional (z.B. @Entity).
Java stellt sieben vordefinierte Annotations zur Verfügung. Dies sind: @Deprecated,
@Override, @SuppressWarnings, @Documented, @Inherited, @Retention,
3.3 Entitäten/Werttypen
21
@Target. Es besteht die Möglichkeit, selbst Annotations zu definieren.
Hibernate verwendet die in JPA definierten Annotations. JPA stellt bereits viele Annotations zur Verfügung. Um alle Feinheiten des Mappings zu ermöglichen, definiert
Hibernate jedoch selbst weitere Annotations.
Die Annotation @Enitity kennzeichnet eine Entitäts-Klasse.
3.2.4
Mapping mit XDoclet
Bei dieser Variante werden die Mapping-Informationen mit XDoclet in den Quellcode
der Klassen integriert und durch geeignete Hibernate-Module zur Laufzeit XML-MappingDateien erzeugt.
Annotations machen diese Technik jedoch überflüssig. Daraus resultiert auch die geringe Bedeutung dieser Variante.
3.3
Entitäten/Werttypen
Hibernate nimmt eine wesentliche Unterscheidung von Elemten vor. Diese wird hier für
alle nachfolgenden Kapitel beschrieben.
Es gibt Entitäts- und Wert-Typen.
Entitäten (engl. Entities) sind Geschäftsobjekte. Also gemappte POJOs auf Java-Seite
bzw. eine Zeile einer Tabelle auf Datenbankseite.
Entitäten besitzen einen Primärschlüssel und durchlaufen einen Lebenszyklus (siehe
Kapitel 3.10.3).
Werttypen hingegeben besitzen keinen Primärschlüssel und somit keine Identität.
Sie sind Teil einer Entität. Werttypen sind somit Spalten einer Tabelle bzw. Instanzvariablen von gemappten Klassen. Nicht gemappte Klassen können als Werttypen verwendet werden.
Diese Unterscheidung ist sinnvoll und wichtig. Wie im Kapitel Objekt-RelationalerBruch beschrieben, ist Java im Gegensatz zu relationalen Datenbanken fein-granular.
Mithilfe von Wertobjekten kann dieses Konzept auf die Datenbank übertragen werden.
3.4 Component Mapping
3.4
22
Component Mapping
Werttypen können in eine Entität eingebettet werden.
Zur Illustrierung wird ein einfaches Beispiel verwendet: Ein Kunde hat eine Addresse.
Es ergeben sich zwei Klassen: Kunde und Addresse. Die Klasse Kunde besitzt ein
Addresse-Objekt.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Entity
@Table(name = "kunde")
class Kunde implements Serializable {
private int k_id;
private String vorname;
private String zuname;
@Embedded
private Addresse adresse;
//...Konstruktoren, Getter/Setter
}
@Embeddable
class Addresse implements Serializable {
private int plz;
private String ort;
private String strasse;
//...Konstruktoren, Getter/Setter
}
Listing 3.3: Annotation Component-Mapping
Die Klasse Kunde kapselt ein Addresse-Objekt. Dieses wird mittels der @EmbeddedAnnotation für Hibernate eingebunden. Wird eine Klasse als Entität deklariert, so geschieht dies mithilfe der Annotation @Entity. Die Annotation @Emmbeddable definiert
eine Werttyp-Klasse. In der Datenbank ergibt sich durch dieses Mapping die Tabelle
kunde. Diese besitzt als Spalten jetzt die Instanzvariablen beider Klassen.
Session s = new AnnotationConfiguration().configure().
buildSessionFactory().openSession();
Transaction tx = s.beginTransaction();
s.save(new Kunde("Max", "Mustermann",
3.4 Component Mapping
23
new Addresse(1010, "Wien", "Stephansplatz 1")));
tx.commit();
Nach Ausführung der obenstehenden Codezeilen ergibt sich folgender Datenbestand:
k_id
1
ort plz strasse
Wien 1010 Stephansplatz 1
vorname zuname
Max
Mustermann
Beim Mapping mit XML sieht die Mapping-Datei für die Klasse Kunde folgendermaßen
aus:
1 <hibernate-mapping>
2
<class name="Kunde" schema="public" table="kunde">
3
<id name="id" type="integer">
4
<column name="k_id"/>
5
<generator class="assigned"/>
6
</id>
7
<property name="vorname" type="string">
8
<column name="vorname"/>
9
</property>
10
11
<property name="zuname" type="string">
12
<column name="zuname"/>
13
</property>
14
15
<component name="adresse" class="Addresse">
16
<property name="ort"/>
17
<property name="plz"/>
18
<property name="strasse"/>
19
</component>
20
</class>
21 </hibernate-mapping>
Listing 3.4: XML Component-Mapping
3.5 Identität
3.5
24
Identität
3.5.1 Übersicht
Jede Entität muss zur Unterscheidbarkeit eine Identität besitzen. Die Identität wird
durch den Primärschlüssel geregelt.
In der Variante des Mappings mit XML wird die Identität einer Entität über das <id>Element beschrieben. Beim Annotation-Mapping wird der Primärschlüssel mittels der
@Id-Annotation gekennzeichnet.
Im Großteil aller Fälle wird als Primärschlüssel ein Zahlenwert in Form von int, long
oder short verwendet.
Der Inhalt des Datensatzes hat keinen Einfluss auf den Primärschlüssel. Man spricht
von einem synthetischen Primärschlüssel.
Der andere Weg ist der fachliche Primärschlüssel. Dieser setzt sich aus Eigenschaften
des Datensatzes zusammen.
3.5.2
Synthetischer Primärschlüssel
Mittels des Tags <generator> kann die Erzeugungsstrategie festgelegt werden.
<generator class="sequence">
<param name="sequence">gegenstand_geg_id_seq</param>
</generator>
Die Angabe des Sequenznamens ist optional.
Beim Annotation-Mapping wird die Erzeugungsstrategie folgend festgelegt:
@Id
@GeneratedValue(generator = "seq")
@GenericGenerator(name = "seq", strategy = "sequence")
Die @GeneratedValue-Annotation ist in JPA definiert. Um alle von Hibernate definierten Strategien nutzen zu können dient die @GenericGenerator-Annotation.
3.5 Identität
25
Hibernate liefert nachstehende Erzeugungsstrategien:
• increment
Diese Strategie ist nur dann geeignet, wenn genau ein Prozess auf die Datenbank zugreift.
Er liefert einen Zahlenwert vom Typ short, long oder int. Der Generator besitzt eine Instanzvariable, welche bei jedem Speichervorgang hochgezählt wird.
Lediglich zur Initialisierung bei der ersten Verwendung muss ein Wert aus der
Datenbank gelesen werden.
Vorteil dieser Strategie ist die gute Performance.
• sequence, identity, hilo und native
Sequence und identity arbeiten ähnlich.
Durch einen Datenbankmechanismus wird beim Speichern ein neuer
Primärschlüssel generiert. Anschließend wird das ID-Attribut des Objekts gesetzt.
Nachteil dieser beiden Versionen ist, das bei jedem Speichervorgang ein zusätzlicher Datenbankzugriff erforderlich wird.
Die Datenbankmechanismen sind nicht Teil des SQL-Standard. Identity wird jedoch von den Datenbanken DB2, MySQL, MS SQL Server, Sybase und HypersonicSQL unterstützt. Sequence wird beispielsweise von Postgres und Oracle
unterstützt.
Hilo arbeitet ähnlich wie increment. Kapselt jedoch keine Instanzvariable. Stattdessen wird eine zusätzliche Schlüsseltabelle verwendet. Darüber hinaus wird
im Speicher ein eigener Wert gehalten. Dadurch muss nicht bei jedem Einfügen
auf die zusätzliche Tabelle zugegriffen werden.
Diese Strategie kann auch bei Mehrbenutzersystemen eingesetzt werden. Des
Weiteren ist sie relativ schnell, da nur selten auf die zusätzliche Tabelle zugegriffen werden muss.
Native verwendet je nach Fähigkeiten des Datenbanksystems eine der drei beschriebenen Erzeugungsstrategien.
• seqhilo
Geht wie hilo vor. Der Unterschied besteht darin, dass statt einer zusätzlichen
Schlüsseltabelle eine Datenbank-Sequenz verwendet wird.
• assigned
Bei dieser Strategie wird der Primärschlüssel händisch“ vergeben. Wie jedes
”
andere Attribut muss auch der Primärschlüssel in der Programmlogik gesetzt
werden.
3.5 Identität
26
• select
Dieser Generator arbeitet mit Datenbanktriggern.
Beim Einfügen eines Datensatzes werden Trigger aktiv, welche dem eingefügten
Datensatz einen Primärschlüssel zuweisen. Anschließend wird dieser Wert von
Hibernate ausgelesen. Dazu verlangt Hibernate jedoch eine zusätzliche UNIQUESpalte, um den eben erst eingefügten Datensatz wiederzufinden.
Diese Strategie macht nur dann Sinn, wenn man aufgrund von Datenbankvorgaben zu dieser Vorgehensweise gezwungen ist.
• foreign
Dieser Generator ist nur in Verbindung mit einer 1:1 oder besser <one-to-one>bzw. @OneToOne-Beziehung anwendbar.
Hierbei wird beiden an der Beziehung beteiligten Entitäten der selbe
Primärschlüssel vergeben. Mehr Informationen dazu finden Sie im Kapitel 3.7.
• uuid - Universally Unique Identifier
Dieser Algorithmus liefert einen 32 Zeichen langen String, welcher abhänging
von der Uhrzeit und IP-Adresse generiert wird.
Der Vorteil dieser Strategie liegt in der Erzeugungsgeschwindigkeit. Immerhin
kommt diese Strategie ohne Datenbankzugriffe aus.
Auf lange Sicht wird das System jedoch verlangsamt, da es sich hierbei um einen
String-Primärschlüssel handelt. Alle Fremdschlüssel auf diese Tabelle müssen
ebenfalls einen String speichern, was einen Overhead darstellt.
• guid - Globally Unique Identifier
Arbeitet ähnlich wie uuid. Der Unterschied besteht darin, dass der eindeutige
String mithilfe von Datenbankmechanismen generiert wird.
Diese Mechanismen unterstützt nur der MS SQL Server und MySQL. Dadurch
ist die Portabilität eingeschränkt. Noch dazu ist ein zusätzlicher Datenbankzugriff
nötig.
Deshalb gibt es eigentlich keinen Grund, diese Strategie zu verwenden.
Die oben beschriebenen Strategien decken die meisten Möglichkeiten bereits ab. Sollte es jedoch notwendig sein, können auch eigene Generatorstrategien implementiert
werden.
3.5.3
Fachlicher Primärschlüssel
Wie bereits erwähnt, setzt sich ein fachlicher Primärschlüssel aus Eigenschaften des
Datensatzes zusammen.
3.5 Identität
27
Grundsätzlich ist diese Form abzulehnen, da es vorkommen kann, dass zwei Datensätze denselben Primärschlüssel liefern. Des Weiteren können bei der Veränderung von Daten Inkosistenzen auftreten.
Es gibt jedoch Fälle, in denen diese Fehler logisch auszuschließen ist, und ein fachlicher Primärschlüssel somit durchaus Sinn macht. Immerhin wird dadurch eine Spalte
in der Tabelle und Aufwand bei der Erzeugung des synthetischen Primärschlüssels
gespart. Im Gegenstand-Beispiel ist dies in Kombination der Gegenstandskurz- und
-langbezeichnung der Fall. Hat ein Gegenstand das gleiche Kürzel und die gleiche
Langbezeichnung handelt es sich um den selben Gegenstand.
Fachliche Primärschlüssel werden in Hibernate als Composite-ID bezeichnet.
Composite-ID
Alle Spalten, welche nicht Teil des Primärschlüssels sind, werden wie üblich in der
Geschäftsklasse gehalten. Zusätzlich kapselt die Geschäftsklasse das Identitätsobjekt. Dieses Objekt kapselt alle Spalten, welche Teil des Primärschlüssels sind.
Zur Unterscheidbarkeit müssen die Methoden equals() und hashcode() in der
Identitätsklasse implementiert werden. Zudem muss sie das Interface
java.io.Serializable implementieren.
Im oben beschriebenen Beispiel bilden die Spalten geg_kurzbez und geg_langbez
die Identitätsklasse. Diese Klasse wird mit dem Namen GegenstandID benannt.
1 public class GegenstandID implements java.io.Serializable {
2
private String gegKurzbez;
3
private String gegLangbez;
4
5
public GegenstandID() {
6
}
7
8
//Getter und Setter ...
9
10
@Override
11
public boolean equals(Object obj) {
12
if (obj == null) {
13
return false;
14
}
15
if (getClass() != obj.getClass()) {
16
return false;
3.5 Identität
17
18
19
28
}
final GegenstandID other = (GegenstandID) obj;
if ((this.gegKurzbez == null) ? (other.gegKurzbez != null) ←: !this.gegKurzbez.equals(other.gegKurzbez)) {
return false;
}
if ((this.gegLangbez == null) ? (other.gegLangbez != null) ←: !this.gegLangbez.equals(other.gegLangbez)) {
return false;
}
return true;
20
21
22
23
24
25
26
27
28
29
30
31
}
32
33
34
35 }
@Override
public int hashCode() {
int hash = 3;
hash = 17 * hash + (this.gegKurzbez != null ? this. ←gegKurzbez.hashCode() : 0);
hash = 17 * hash + (this.gegLangbez != null ? this. ←gegLangbez.hashCode() : 0);
return hash;
}
Listing 3.5: Zusaetzliche Identifikationsklasse beim fachlichen Primaerschluessel
Wie oben beschrieben, kapselt die Geschäftsklasse dieses Identitätsobjekt. Somit wird
eine Instanzvariable vom Typ GegenstandID hinzugefügt.
Der synthetische Primärschlüssel gegId wird nicht mehr benötigt. Ansonsten folgt diese Klasse den normalen Regeln einer Entitäts-Klasse.
1 public class Gegenstand {
2
3
private GegenstandID id;
4
private String gegUpiskurzbez;
5
private Character gegGarKurzbez;
6
private Integer gegNotenattrArt;
7
private Integer gegMspId;
8
private Integer gegFremdsprbilddok;
9
10
public Gegenstand() {
11
}
12
13
//...
14
//Getter und Setter
3.5 Identität
15
16
17 }
29
//...
Listing 3.6: Persistenzklasse beim Fachlichen Primaerschluessel
Zum Mappen des fachlichen Primärschlüssels wird beim XML-Mapping der
<composite-id>-Tag verwendet. Dieser kann beliebig viele <key-property>-Tags
aufnehmen. Das <key-property>-Element beschreibt eine Spalte des fachlichen
Primärschlüssels. Es werden hier alle Instanzvariablen der Identitätsklasse beschrieben bzw. angegeben.
1 <composite-id name="id">
2
<key-property name="gegKurzbez" column="geg_kurzbez"/>
3
<key-property name="gegLangbez" column="geg_langbez"/>
4 </composite-id>
Listing 3.7: Fachlicher Primaerschluessel in der Mapping-Datei
Beim Annotation-Mapping erfolgt das Mappen eines zusammengesetzten
Primärschlüssels nach demselben Konzept.
Hier wird die ID-Klasse mit der @Embeddable-Annotation definiert. In der Geschäftsklasse Gegenstand wird diese ID-Klasse mittels @EmbeddedId gekennzeichnet.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
@Table(name = "gegenstand")
public class Gegenstand {
private
private
private
private
private
String gegUpiskurzbez;
Character gegGarKurzbez;
Integer gegNotenattrArt;
Integer gegMspId;
Integer gegFremdsprbilddok;
@EmbeddedId
private GegenstandID id;
//...
}
@Embeddable
public class GegenstandID implements java.io.Serializable {
private String gegKurzbez;
3.6 Collections
20
21
22
23
24
25
26 }
30
private String gegLangbez;
public GegenstandID() {
}
//...
Listing 3.8: Annotation-Mapping des fachlichen Primaerschluessels
3.6
Collections
Hibernate unterstützt alle in Abbildung 3.3 ersichtlichen Collections. Des Weiteren werden auch Java-Arrays unterstützt.
Abbildung 3.3: Vererbungshierarchie von Collections und Maps
3.6 Collections
31
Um mehrwertige Datenbankbeziehungen auf die Java-Welt umzusetzen, werden Collections verwendet. Dies wird im nachfolgenden Kapitel 3.7 gezeigt.
Die Verwendung von Collections beschränkt sich jedoch nicht auf die Abbildung von
Assoziationen und deren Entitäten. Es können auch Collections von Wertobjekten definiert werden.
JPA definiert kein Collection-Mapping von Wertobjekten. Die Hibernate-spezifische Annotation ist @CollectionOfElements.
Es ist zu beachten, dass eine Collection in der Javaklasse mit dem Interface-Typen
deklariert werden muss.
List<Muttersprache> muttersprachen = new Vector<Muttersprache>();
Es gibt nachstehende Collection-Mappings.
• <list>
Hier wird eine java.util.List verwendet. Konkret handelt es sich daher um
ArrayList, Vector oder LinkedList.
In einer List wird die Einfügereihenfolge beibehalten. Um dies zu gewährleisten
wird eine zusätzliche Indexspalte in der Tabelle benötigt. Diese Spalte wird über
das Element <list-index> bzw. der @IndexColumn-Annotation angegeben.
Dabei muss es sich um eine Spalte vom Typ Integer handeln.
Häufig ist die Einfügereihenfolge jedoch egal. Dort sind andere Varianten vorzuziehen, um den Overhead einer Zusatzspalte zu vermeiden.
• <bag>
Hierbei handelt es sich genau wie bei <list> um eine java.util.List. Einziger Unterschied besteht darin, dass die Reihenfolge egal ist. Somit wird auch
keine zusätzliche Indexspalte benötigt.
• <set>
Es handelt sich um eine Implementierung von java.util.Set.
Im Gegensatz zu List kann ein Objekt in einem Set nur einmal vorkommen.
Zu diesem Zweck sollten die Methoden equals() und hashCode() sinnvoll
überschrieben werden. Die Reihenfolge, in der die Objekte gespeichert werden, ist von den Hashcodes abhängig. Will man eine bestimmte Sortierreihenfolge festlegen, wird ein SortedSet verwendet. Die Reihenfolge kann über einen
Comparator bestimmt werden. Dieser wird im Mapping mithilfe des
3.6 Collections
32
sort-Parameters angegeben. Wird dieser Parameter angegeben, muss die Instanzvariable im POJO als SortedSet deklariert werden.
• <array> und <primitive-array>
Hierbei handelt es sich um einfache Java-Arrays. Das Mapping ist ident zu <list>.
• <map>
Eine Map speichert Schlüssel-Werte-Paare. Auf Datenbankseite ist der Schlüssel
ein Foreign-Key und der Wert ein bzw. mehrere Spalten einer anderen Tabelle.
Die Instanzvariablen im POJO können als java.util.Map oder
java.util.SortedMap deklariert werden.
Beim Annotation-Mapping sind die oben gezeigten Unterscheidungen nicht nötig. Die
Mapping-Informationen dazu sind bereits über die Deklaration der Instanzvariable gegeben.
private List collection1;
Bei allen Collection-Mappings gibt das key-Element die Datenbankspalte mit dem
Fremdschlüssel an. Beim Annotation-Mapping wird dieser über @JoinTable definiert.
Werden Collections von Wertobjekten verwendet, so gibt der element-Tag an, welche
Spalte als Wert geladen werden soll. Es ist ebenfalls möglich, mittels
<composite-element> mehrere Wertetypen (also mehrere Spalten) zu laden. Beim
Annotation-Mapping wird hierfür die @Column-Annotation verwendet.
Wichtige Paramater, welche bei fast allen Collection-Mappings konfigurierbar sind,
werden nachstehend erklärt.
• lazy
Über diesen Parameter kann die Fetching-Strategie konfiguriert werden.
Standardmäßig ist das sogenannte Lazy Fetching aktiviert. Mehr Informationen
dazu gibt es im Kapitel 3.14.
• batch-size
Definiert die Anzahl der Elemente, die beim Batch-Fetching aus der Datenbank
geladen werden. Standardmäßig ist dieser Parameter auf 1 gesetzt. Dies entspricht der Select-Fetching Strategie. Dabei ist auf das sogenannte n+1-Problem
zu achten. Mehr Informationen dazu gibt es wieder im Kapitel 3.14.
• sort
Wie oben beschrieben, kann eine Collection durch diesen Parameter sortiert werden.
3.7 Beziehungen
33
Wird er auf natural gesetzt, so wird die natürliche Reihenfolge der Elemente
verwendet. Hier kann aber auch eine Comporator-Klasse angegeben werden.
Standardmäßig wird keine Sortierung durchgeführt.
• cascade
Wird eine Entität persistiert, welche Collections anderer Entitäten referenziert,
werden diese standardmäßig nicht mitgespeichert, verändert oder gelöscht.
Mithilfe dieses Parameters kann dies verändert werden. Weitere Informationen
zur Kaskadierung referenzierter Objekte gibt es im Kapitel 3.7.
• order-by
Hier kann eine Tabellenspalte angegeben werden, nach welcher sortiert werden
soll. Optional kann nach dem Spaltennamen noch asc oder desc angegeben
werden. Bei asc (engl. ascending) wird aufsteigend, bei desc (engl. descending)
absteigend sortiert.
• where
Mithilfe dieses Parameters können Kriterien für das Einladen von Entitäten festgelegt werden. Z.B.: where="geg_msp_id=12"
3.7
Beziehungen
3.7.1 Übersicht
Im Mapping können Assoziationen zwischen Entitäten definiert werden.
Zur Erklärung der Beziehungstypen werden in diesem Kapitel die Namens- und Zeichnungskonventionen von ER4 -Modellen nach ISO5 -Standard verwendet.
Binär/Reflexiv
Grundsätzlich ist zwischen binären und reflexiven Beziehungen zu unterscheiden.
Binäre Beziehungen sind Verbindungen zwischen verschiedenen Entitätstypen, während
reflexive Beziehungen Verbindungen eines Entitätstyps zu sich selbst sind.
4
5
Entity Relationship
International Organization for Standardization
3.7 Beziehungen
34
Abbildung 3.4: binaere/reflexive Beziehungen
Unidirektional/Bidirektional
Des Weiteren wird zwischen unidirektional und bidirektional unterschieden.
Bei einer bidirektionalen Beziehung besteht die Verbindung in beiden Richtungen. D.h.
eine Entität kann auf die Andere schließen und umgekehrt.
Bei unidirektionalen Beziehungen besteht die Verbindung in nur einer Richtung.
Auf Datenbankseite ist jede Beziehung bidirektional. Von jeder Seite der Beziehung
kann auf die Andere geschlossen werden. Im Objektmodell von Java sind diese Rückschlüsse nicht möglich.
Hibernate erlaubt die Verwendung bidirektionaler Beziehungen in Java.
Dabei wird in beiden Mappings, der an der Beziehung beteiligten Entitäten, eine Referenz angelegt.
Hibernate muss dabei wissen, welche Seite der Beziehung die Objekte in die Datenbank schreibt. Schließlich sind durch diese Form der Implementierung Dateninkonsistenzen möglich und Entitäten würden doppelt abgespeichert.
Zu diesem Zweck gibt es die sogenannte owning“-Seite, welche für die Persistierung
”
zuständig ist.
Die andere Seite der Beziehung kann die Entitäten lesen, jedoch nicht speichern.
Beim Annotation-Mapping kann die owning-Seite mittels des Parameters mappedBy
definiert werden. Beim XML-Mapping dient hierzu der Parameter inverse.
In den meisten Fällen ist es jedoch nicht sinnvoll, den Zusatzaufwand einer biderektionalen Beziehung zu haben. Stattdessen kann bei Bedarf eine Query abgesetzt werden.
3.7 Beziehungen
35
In diesem Kapitel wird ein Beispiel einer bidirektionalen Beziehung gezeigt.
3.7.2
Kardinalitäten
Die Kardinalität beschreibt den Grad der Beziehung. Es wird auf Datenbankseite, und
somit auch von Hibernate, zwischen folgenden Kardinalitäten unterschieden:
• 1:n / n:1
Entspricht many-to-one / one-to-many bzw. @ManyToOne und @OneToMany
• 1:1
Entspricht one-to-one bzw. @OneToOne
• m:n
Entspricht many-to-many bzw. @ManyToMany
Zu jedem Beziehungstyp stellt Hibernate zusätzliche (optionale) Parameter zur Verfügung.
Die Wichtigsten sind:
• lazy
Dieser Parameter konfiguriert das Verhalten von Hibernate in Bezug auf die Ladestrategie. true und false sind mögliche Werte.
Wird dieser Parameter mit false gesetzt, so tritt das sogenannte Eager Loading in Kraft und die/das referenzierte(n) Objekt(e) werden sofort mitgeladen.
Mehr Informationen dazu gibt es im Kapitel 3.14.
• cascade
Wird eine Entität auf die Datenbank geschrieben, werden dessen referenzierten
Entitäten standardmäßig nicht mitgeschrieben.
Wird beispielsweise im Gegenstand-Beispiel die Muttersprache als Objekt mitgeladen und dabei verändert (z.B. wird der Name der Mutterprache von Englisch
auf english geändert), hätte das beim Speichern des Gegenstands keine Auswirkung.
Zu diesem Zweck gibt es den Parameter cascade. Dieser kann mit create,
merge, save-update, delete, lock, refresh, evict und replicate gesetzt werden. Sollen alle diese Operationen durchgeführt werden, gibt es zusätzlich den Wert all.
Zu beachten ist, dass dieser Parameter mit Vorsicht zu genießen ist und nur dann
eingesetzt werden sollte, wenn es wirklich sein muss. Schließlich wird sonst bei
jeder Änderung eines Objekts eine zusätzliche Änderung auf die referenzierten
Objekten durchgeführt. Dies kann zu ungewollten Änderungen und erheblichen
Performanceverlusten führen.
3.7 Beziehungen
3.7.3
36
many-to-one / one-to-many
Ein normaler“ Fremdschlüssel auf Datenbankseite stellt eine 1:n-Beziehung dar. Die”
se Kardinalität kommt daher am häufigsten vor.
Beliebig viele Datensätze können den selben Fremdschlüssel besitzen und somit den
selben Datensatz referenzieren.
Im Gegenstand-Beispiel ist dies beispielsweise bei der Muttersprache der Fall. Bis
jetzt wurde lediglich der Wert, also der Fremdschlüssel als Wert geladen. Will man beispielsweise aber den Namen der Muttersprache anzeigen, ist es sinnvoll, die MutterspracheEntität mitzuladen.
Viele (engl. many) Gegenstände können eine (engl. one) Mutterprache haben.
Abbildung 3.5: 1:n-Beziehung
Die 1:n-Beziehung entspricht dem <many-to-one>-Tag im Mapping.
1 <many-to-one name="muttersprache" class="Muttersprache" lazy=" ←false">
2
<column name="geg_msp_id" not-null="true"/>
3 </many-to-one>
Listing 3.9: XML-Mapping bei einer 1:n-Beziehung
Der Parameter column gibt den Namen der Datenbankspalte an. Mit name wird der
Name des Objektes im POJO angegeben.
Wie oben beschrieben, können auch zusätzliche Parameter angegeben werden. In
diesem Beispiel wird Lazy Loading deaktiviert.
Diese Beziehung besteht in nur einer Richtung. Sie ist somit unidirektional.
Eine bidirektionale Beziehung wird erreicht, indem auch im Mapping auf Seiten der
Muttersprache eine Beziehung angelegt wird.
Aus Sicht der Muttersprache stellt die Assoziation zu Gegenstand eine n:1-Beziehung
dar.
3.7 Beziehungen
37
Hier wird one-to-many verwendet. Eine (engl. one) Muttersprache kann mehrere (engl.
many) Gegenstände haben.
Abbildung 3.6: n:1-Beziehung
Es handelt es sich um eine mehrwertige Beziehung. Somit muss eine Collection verwendet werden.
1 <set inverse="true" name="gegenstaende">
2
<key>
3
<column name="geg_msp_id" not-null="true"/>
4
</key>
5
<one-to-many class="Gegenstand"/>
6 </set>
Listing 3.10: XML-Mapping bei einer n:1-Beziehung
Wie anfangs beschrieben, ist bei bidirektionalen Beziehungen eine Seite für die Persistierung der Entitäten zuständig ( owning“-Seite). Die andere Seite darf die Entitäten
”
zwar lesen, jedoch nicht speichern.
Im Collection-Mapping wird der Parameter inverse auf true gesetzt. Dadurch ist
dies die lesende Seite der Beziehung. Änderungen wirken sich nur dann aus, wenn
sie auf Seite einer Gegenstand-Entität gemacht werden.
3.7.4
one-to-one
Jeder Entität ist höchstens eine andere Entität zugeordnet. Dieser Beziehungstyp ist
eher selten und stellt eine Sonderform dar.
Beispiel für diesen Beziehungstyp:
Ein Mann ist höchstens mit einer Frau verheiratet und umgekehrt.
3.7 Beziehungen
38
Abbildung 3.7: 1:1-Beziehung
Frau und Mann sind dabei unterschiedliche Entitätstypen und stehen daher auch in unterschiedlichen Tabellen. Somit ist diese Beziehung binär. Würden Frauen und Männer
in einer Tabelle, z.B. Personal, stehen würde es sich um eine reflexive Beziehung handeln.
Auf Datenbankseite wird eine echte 1:1-Beziehung mit demselben Primärschlüssel
dargestellt. Daraus ergibt sich, dass eine echte 1:1-Beziehung nicht reflexiv sein kann.
Denn das würde eine Primärschlüssel-Verletzung bedeuten.
Eine 1:1-Beziehung wird in der Mapping-Datei folgendermaßen realisiert:
<one-to-one name="frau">
Wie im Kapitel Identität beschrieben, gibt es die Generatorstrategie foreign. Diese
wird hier eingesetzt um den in Beziehung stehenden Entitäten Frau und Mann denselben Primärschlüssel zu vergeben.
Um eine bidirektionale-Beziehung anzulegen, dient der Parameter property-ref.
Beim Annotation-Mapping gibt es für eine 1:1-Beziehung die @OneToOne-Annotation.
Wird der jeweiligen Instanzvariable neben dieser Annotation auch
@PrimaryKeyJoinColumn beigefügt, so geht Hibernate von einer echten 1:1 Beziehung aus. Dadurch wird für beide an der Referenz beteiligten Entitäten derselbe
Primärschlüssel verwendet bzw. vergeben.
3.7.5
many-to-many
Eine m:n-Beziehung erfordert immer eine Verbindungstabelle. Diese Tabelle wird als
Kreuztabelle bezeichnet.
In der Kreuztabelle stehen üblicherweise nur Primärschlüssel beider Tabellen, welche
an der Beziehung beteiligt sind.
3.7 Beziehungen
39
In relationalen Datenbanken hat sich der Standard etabliert, die Kreuztabelle folgendermaßen zu benennen: Name der ersten Tabelle, ein Unterstrich und anschließend
der Name der zweiten Tabelle.
Eine Kreuztabelle muss in Hibernate nicht gemappt werden.
Illustriert wird die m:n-Beziehung wieder am Gegenstand-Beispiel. Man nehme an,
dass ein Gegenstand in mehreren Sprachen unterrichtet werden kann.
Abbildung 3.8: m:n-Beziehung
Die Kreuztabelle wird mit gegenstand_muttersprache bezeichnet. Sie enthält im
einfachsten Fall zwei Spalten. Dies sind die beiden Primärschlüssel der an der Beziehung beteiligten Entitäten.
In diesem Beispiel sind das die Spalten geg_id (Primärschlüsselspalte von Gegenstand) und msp_id (Primärschlüsselspalte von Muttersprache).
Es ergibt sich folgendes Mapping:
1 <set name="muttersprachen" table="gegenstand_muttersprache">
2
<key column="geg_id"/>
3
<many-to-many class="Muttersprache" column="msp_id"/>
4 </set>
Listing 3.11: XML-Mapping einer m:n-Beziehung
Im key- und many-to-many-Tag werden die für die Beziehung benötigten beiden
Spalten über das column-Attribut angegeben.
Hier wird ersichtlich, dass die Kreuztabelle nicht gemappt werden muss. Des Weiteren
ist es auch möglich, mehr als zwei Spalten in der Kreuztabelle zu haben.
Durch obiges Mapping wird auf Gegenstand-Seite eine Collection vom Typ
Muttersprache geladen. Soll diese Beziehung bidirektional sein, ist dies wie bei der
1:n/n:1-Beziehung gezeigten Form mittels inverse bzw. @mappedBy möglich.
3.8 Vererbung
40
Beim Annotation-Mapping gibt es für m:n-Beziehungen die @ManyToMany-Annotation.
1
2
3
4
5
6
7
8
public class Gegenstand{
...
private Set<Muttersprache> muttersprachen;
...
@ManyToMany
public Set<Muttersprache> getMuttersprachen(){
return muttersprachen;
}
Listing 3.12: Annotation-Mapping einer m:n-Beziehung
3.8
Vererbung
3.8.1 Übersicht
Vererbung ist ein Grundelement jeder objektorientierten Programmiersprache. Wie im
Kapitel Objekt-Relationaler-Bruch beschrieben, gibt es das Konzept der Vererbung bei
relationalen Datenbanken nicht.
Hibernate unterstützt Vererbung bereits ab der allerersten Version. Bei JPA wurde dies
erst mit der EJB 3.0-Spezifikation hinzugefügt. Bei der Implementierung in JPA hatte
Hibernate Vorbildwirkung. Daher unterstützen Hibernate und JPA dieselben Technologien.
Zur Abbildung von Vererbungshierarchien auf relationale Datenbanken gibt es folgende
drei Strategien:
• Tabelle pro Klassenhierarchie
• Tabelle pro Subklasse
• Tabelle pro konkreter Klasse
Diese Strategien sind in JPA mittels der Enumeration InheritanceType deklariert.
Diese Enumeration besitzt die Werte SINGLE TABLE, JOINED und TABLE PER CLASS.
Mit der @Inheritence-Annotation kann die Vererbungsstrategie festgelegt werden.
3.8 Vererbung
41
@Inheritance(strategy = InheritanceType.JOINED)
In diesem Kapitel werden die Konzepte anhand des Mappings mit XML gezeigt. Auf
das Annotation-Mapping wird nicht näher eingegangen. Die Konzepte sind bei beiden
Varianten gleich.
Um die verschiedenen Strategien zu verdeutlichen, wird das Gegenstand-Beispiel herangezogen. Dabei ist die Klasse Gegenstand abstrakt. Sie kapselt die drei Felder
id, kurzBez und langBez. Die konkreten Klassen TheoretischerGegenstand
und FachpraktischerGegenstand erben von Gegenstand und implementieren
so diese drei Eigenschaften.
Abbildung 3.9 verdeutlicht diesen Sachverhalt.
Abbildung 3.9: Vererbungshierarchie Gegenstand
Hierbei handelt es sich jedoch nur um ein theorethisches Beispiel. Tatsächlich existiert
diese Vererbungshierarchie im Projekt SAS III nicht.
Es ergeben sich nachfolgende Klassen:
1 abstract class Gegenstand {
2
int id;
3
String kurzBez;
4
String langBez;
3.8 Vererbung
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
42
//Konstruktor, Getter und Setter...
}
class TheoretischerGegenstand extends Gegenstand {
String lehrsaal;
//Konstruktor, Getter und Setter...
}
class FachpraktischerGegenstand extends Gegenstand{
String werkstaette;
//Konstruktor, Getter und Setter...
}
Listing 3.13: Implementierung der Vererbungshierarchie Gegenstand
3.8.2
Tabelle pro Klassenhierarchie (SINGLE TABLE)
Bei dieser Strategie werden alle Klassen, die innerhalb einer Vererbungslinie liegen,
auf eine einzige Tabelle abgebildet.
Polymorphie wird mithilfe einer zusätzlichen Spalte implementiert. In dieser steht der
Name des konkreten Objektes, welches zur Laufzeit gespeichert wurde.
Beim Mapping dieser Strategie sind die Eigenschaften discriminator und subclass
von entscheidender Bedeutung.
1 <hibernate-mapping>
2
<class name="Gegenstand" schema="public" table="gegenstand">
3
<id name="id" type="int">
4
<column name="id"/>
5
<generator class="sequence"/>
6
</id>
7
8
<discriminator column="gegenstand_type" type="string"/>
9
10
<property name="kurzBez" type="string">
11
<column name="kurzbez"/>
12
</property>
3.8 Vererbung
13
14
15
16
17
18
19
20
21
22
23
24
43
<property name="langBez" type="string">
<column name="langbez"/>
</property>
<subclass discriminator-value="Theoretischer Gegenstand" ←name="TheoretischerGegenstand">
<property name="lehrsaal" column="lehrsaal"/>
</subclass>
<subclass discriminator-value="Fachpraktischer Gegenstand" ←name="FachpraktischerGegenstand">
<property name="werkstaette" column="werkstaette"/>
</subclass>
</class>
</hibernate-mapping>
Listing 3.14: Tabelle pro Klassenhierarchie
Das discriminator-Element in Zeile 8 gewährleistet Polymorphie.
Zu diesem Zweck wird die Spalte gegenstand_type angelegt. Diese Spalte kapselt
den Namen des Objekttyps, welcher zur Laufzeit in die Datenbank geschrieben wurde.
Die zwei subclass-Tags binden die Klassen der Vererbungshierarchie ein.
Das Attribut discriminator-value setzt den Objekttyp-Namen der gegenstand_typeSpalte.
Es ergibt sich folgende Tabelle gegenstand. Diese wird zur Illustration mit Beispieldaten gefüllt:
id
1
2
gegenstand_type kurzbez
Fachpraktischer ..
Schweissen
Theoretischer ..
Maschinen
langbez
lehrsaal werkstaette
Schutzgasschweissen L205
null
Maschinenkunde
null
Trakt 3b
Vor- und Nachteile
Größter Vorteil dieser Strategie ist die Einfachheit.
Auf den ersten Blick ist auch die Performance ein weiterer Vorteil.
Jede Abfrage greift auf genau eine Tabelle zu. Es werden keine Joins benötigt. Jedoch
wächst diese Tabelle bei größeren Vererbungshierarchien mit. Dabei kann es zu sehr
3.8 Vererbung
44
großen Tabellen mit mehreren Hundert Spalten kommen und das wirkt sich entsprechend negativ auf die Performance aus.
Ein weiterer Nachteil dieser Version sind redundante Spalten. Während bei einem
FachpraktischenGegenstand die Spalte lehrsaal leer bleibt, ist dies beim
TheoretischenGegenstand bei der Spalte werkstaette der Fall. Bei größeren
Vererbungshierarchien würde dieses Problem in noch größerem Ausmaß auftreten.
Des Weiteren wird durch redundante Spalten die Datenbankintegrität verletzt, da Spalten nullable sein müssen.
Sinn macht diese Strategie daher bei kleinen Vererbungshierarchien.
3.8.3
Tabelle pro Subklasse (JOINED)
Bei dieser Vererbungsstrategie wird für jede Subklasse, d.h. abstrakte und konkrete
Klasse, eine Tabelle verwendet.
Die Subklassen der Vererbungshierarchie werden über den <joined-subclass>Tag eingebunden.
1 <hibernate-mapping>
2
<class name="Gegenstand" table="gegenstand">
3
<id name="id" type="int">
4
<column name="id"/>
5
<generator class="sequence"/>
6
</id>
7
8
<property name="kurzBez" type="string">
9
<column name="kurzbez"/>
10
</property>
11
<property name="langBez" type="string">
12
<column name="langbez"/>
13
</property>
14
15
<joined-subclass name="TheoretischerGegenstand" ←table="theoretischergegenstand">
16
<key column="gegenstand_id"/>
17
<property name="lehrsaal" column="lehrsaal"/>
18
</joined-subclass>
19
20
<joined-subclass name="FachpraktischerGegenstand" ←table="fachpraktischergegenstand">
3.8 Vererbung
45
21
<key column="gegenstand_id"/>
22
<property name="werkstaette" column="werkstaette"/>
23
</joined-subclass>
24
</class>
25 </hibernate-mapping>
Listing 3.15: Tabelle pro Subklasse
Hierbei werden drei Tabellen verwendet.
In der gegenstand-Tabelle gibt es den Primärschlüssel id und die beiden Spalten
kurzBez und langBez. Also genau jene Eigenschaften der Gegenstand-Klasse.
Des Weiteren gibt es die Tabellen theoretischergegenstand und
fachpraktischergegenstand, bei welchen der Primärschlüssel zeitgleich Fremdschlüssel auf die Super“-Tabelle Gegenstand ist.
”
In diesem Beispiel ist dies die Spalte gegenstand_id. Diese wird über den key-Tag
gesetzt.
Es ergeben sich somit folgende drei Tabellen:
Tabelle gegenstand:
id kurzbez
1
Schweissen
2
Maschinen
langbez
Schutzgasschweissen
Maschinenkunde
Tabelle fachpraktischergegenstand:
gegenstand_id werkstaette
1
Trakt 3b
Tabelle theoretischergegenstand:
gegenstand_id
2
lehrsaal
L205
Vor- und Nachteile
Vorteil dieser Strategie ist, dass im Gegensatz zur Strategie Tabelle pro Subklasse, die
Datenbankintegrität gewährleistet wird, da keine Attribute nullable sein müssen.
3.8 Vererbung
46
Der Nachteil liegt in der Performance. Die Daten eines Gegenstands liegen verteilt
auf mehreren Tabellen. Will man einen bestimmten Gegenstand laden, so wird immer
ein inner-join notwendig. Will man eine Abfrage aller Gegenstände durchführen,
so wird ein outer-join notwendig.
3.8.4
Tabelle pro konkreter Klasse (TABLE PER CLASS)
Hierbei wird für jede konkrete Klasse der Vererbungshierarchie eine Tabelle verwendet.
Für Interfaces und abstrakte Klassen gibt es somit keine Datenbanktabelle.
In diesem Beispiel werden also zwei Tabellen verwendet:
fachpraktischerGegenstand und theoretischerGegenstand.
Diese beinhalten jeweils neben ihren eigenen“ auch die geerbten Attribute. In diesem
”
Beispiel also id, kurzBez, und langBez.
Um die Attribute der Superklassen in die konkrete Klasse einzubinden dient der Tag
<union-subclass>-Tag.
1 <hibernate-mapping>
2
<class name="Gegenstand" abstract="true">
3
<id name="id" type="int">
4
<column name="geg_id"/>
5
<generator class="sequence"/>
6
</id>
7
8
<property name="kurzBez" type="string">
9
<column name="geg_kurzbez"/>
10
</property>
11
<property name="langBez" type="string">
12
<column name="geg_langbez"/>
13
</property>
14
15
<union-subclass name="TheoretischerGegenstand" ←table="theoretischergegenstand">
16
<property name="lehrsaal" column="lehrsaal"/>
17
</union-subclass>
18
19
<union-subclass name="FachpraktischerGegenstand" ←table="fachpraktischergegenstand">
20
<property name="werkstaette" column="werkstaette"/>
21
</union-subclass>
3.8 Vererbung
47
22
</class>
23 </hibernate-mapping>
Listing 3.16: Tabelle pro konkreter Klasse
Beim Forward Engineering werden hier zunächst drei Tabellen angelegt.
Neben fachpraktischergegenstand und theoretischergegenstand wird auch
eine Tabelle zur abstrakten Klasse Gegenstand erzeugt.
Dies ist jedoch nicht erwünscht. Schliesslich wird diese abstrakte“ Tabelle ja auch
”
nie verwendet. Daher muss Hibernate gesagt werden, dass die Klasse Gegenstand
abstrakt ist. Dazu dient die Eigenschaft abstract="true" im class-Tag.
Es ergeben sich somit folgende Tabellen:
Tabelle theoretischergegenstand:
id
2
kurzbez langbez
lehrsaal
Maschinen Maschinenkunde L205
Tabelle fachpraktischergegenstand:
id kurzbez
1
Schweissen
langbez
werkstaette
Schutzgasschweissen Trakt 3b
Vor- und Nachteile
Probleme dieser Variante ergeben sich bei polymorphen Abfragen.
Eine Abfrage über Gegenstände beliebigen konkreten Typs werden i.d.R. relativ komplex.
Des Weiteren ist die Vererbung in der Datenbank nicht mehr ersichtlich.
Vorteil dieser Variante ist, dass der Zugriff auf eine bestimmte konkrete Klasse einfach
und performant ist.
3.9 Konfiguration
3.9
48
Konfiguration
3.9.1 Übersicht
Hibernate ist dafür ausgelegt, in verschiedenen Umgebungen zu arbeiten.
Dafür benötigt es Informationen über beispielsweise den verwendeten Datenbanktreiber, den Datenbankdialekt und viele andere.
Zu diesem Zweck kann ein Objekt vom Typ org.hibernate.cfg.Configuration
instanziert werden, welches diese Informationen kapselt.
Es gibt verschiedene Möglichkeiten, dieses Objekt zu initialisieren. Diese werden in
den nachfolgenden Kapiteln beschrieben.
3.9.2
hibernate.properties
Bei der Erzeugung eines Configuration-Objekts versucht Hibernate, die
hibernate.properties-Datei einzulesen.
Kann die Datei gefunden werden, wird das Objekt mit den darin gespeicherten Werten initialisiert. Die Datei muss sich zu diesem Zweck in der Wurzel des Classpaths
befinden.
1
2
3
4
5
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.connection.driver_class=org.postgresql.Driver
hibernate.connection.username=sas
hibernate.connection.password=sasii
hibernate.connection.url=jdbc:postgresql://localhost:5432/SASDB
Listing 3.17: Die Konfigurationsdatei hibernate.properties
Eine Zeile dieser Datei wird als Property bezeichnet und entspricht einem
java.util.Properties-Objekt.
Diese Art der Konfiguration ist dann sinnvoll, wenn man mit genau einer Datenbank
arbeitet.
Der große Nachteil besteht darin, dass es nicht möglich ist, die Mapping-Dateien in
dieser Datei anzugeben. Diese müssen mit entsprechenden Methoden im Java-Code
hinzugefügt werden.
3.9 Konfiguration
3.9.3
49
hibernate.cfg.xml
Es ist mögich, die gesamte Konfiguration in einer Datei anzugeben. Dies geschieht in
Form einer XML-Datei.
Das Configuration-Objekt besitzt die Methode configure(). Wird diese Methode aufgerufen, wird das Configuration-Objekt mit den Informationen dieser Datei
gefüllt. In diesem Kontext muss die Datei im Hauptverzeichnis der Quelldateien liegen.
D.h. die Konfigurationsdatei muss im default-Package liegen.
Es ist aber ebenfalls möglich, den Pfad der Konfigurationsdatei als Parameter zu übergeben: configure(File configFile).
1 <?xml version="1.0" encoding="UTF-8"?>
2 <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate ←Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/ ←hibernate-configuration-3.0.dtd">
3
4 <hibernate-configuration>
5
<session-factory>
6
<property name="hibernate.dialect">
7
org.hibernate.dialect.PostgreSQLDialect</property>
8
<property name="hibernate.connection.driver_class">
9
org.postgresql.Driver</property>
10
<property name="hibernate.connection.url">
11
jdbc:postgresql://localhost:5432/SASDB</property>
12
<property name="hibernate.connection.username">sas</property>
13
<property name="hibernate.connection.password">sas</property>
14
15
<mapping resource="model/Gegenstand.hbm.xml"/>
16
</session-factory>
17 </hibernate-configuration>
Listing 3.18: Die Konfigurationsdatei hibernate.cfg.xml
Das Wurzelelement dieser XML-Datei ist <hibernate-configuration>.
Darunter befindet sich der Tag <session-factory>. In diesem sind die Einstellungen über die Tags <property> und <mapping> realisiert.
In diesem Beispiel wird in Zeile 12 die Entität Gegenstand eingebunden.
Das Einbinden von Entitäten kann auf verschiedene Weise erfolgen. Insbesondere ist
es auch möglich, Entitäten, welche in externen Dateien gemappt werden, in das Projekt einzubinden.
3.9 Konfiguration
50
Der Vorteil dieser Variante ist, dass alle Konfigurationseinstellungen in einer einzigen
Datei angegeben werden können.
Des Weiteren ist es möglich, mehrere dieser Konfigurationsdateien verwenden zu
könnnen. Der Methode configure(File configFile) kann die benötigte Kofigurationsdatei übergeben werden.
Dies ist vor allem dann sinnvoll, wenn man mit mehreren Datenbanken arbeitet, oder
mit unterschiedlichen Einstellungen auf eine Datenbank zugreifen will.
3.9.4
Zusätzliche Methoden
Auch im Java-Quellcode ist es möglich die Konfiguration aufzbauen und zu verändern.
Die Klasse Configuration liefert zu diesem Zweck untenstehende Methoden. Alle
diese Methoden liefern ein Configuration-Objekt zurück.
Hinzufügen bzw. ändern von Properties:
• setProperty(String propertyName, String value)
Verändert den Property-Eintrag.
• addProperties(Properties extraProperties)
Fügt Properties hinzu.
• setProperties(Properties properties)
Entfernt alle bereits existerenden Property-Einträge und ersetzt sie durch die
Übergebenen.
Die letzten beiden Methoden bekommen ein oder mehrere java.util.PropertiesObjekte übergeben. Wie oben beschrieben entspricht ein solches Objekt einer Zeile in
der hibernate.properties bzw. hibernate.cfg.xml.
Wie bereits erwähnt ist es in der Datei hibernate.properties nicht möglich MappingDateien einzubinden. Als Ersatz können dafür nachfolgende Methoden verwendet werden:
• addFile(String xmlFile)
Diese Methode erwartet entweder einen String mit dem Dateinamen oder ein
File-Objekt.
• addDirectory(File dir)
Bindet alle Mapping - Dateien ein, die sich im angegebenen Verzeichnis befinden.
3.9 Konfiguration
51
1 new Configuration().
2
setProperty("hibernate.connection.url", "jdbc:postgresql ←://10.0.110.1:5432/SASDBSERVER").
3
addFile("model/Gegenstand.hbm.xml");
Listing 3.19: Setzen der Konfiguration im Quellcode
In diesem Beispiel wird in Zeile 1 ein Configuration-Objekt instanziert. Hibernate
versucht dieses Objekt standardmäßig mit den Werten der hibernate.properties-Datei
zu initialisieren.
Mit der Methode setProperty(String propertyName, String value) wird
anschließend der Datenbankpfad geändert. Hier wird ersichtlich, dass alle Methoden
ein Configuration-Objekt zurückliefern. Im nächsten Schritt wird mit der Methode
addFile(String xmlFile) das Gegenstands-Mapping eingebunden.
3.9.5
Konfiguration beim Mapping mit Annotations
Wurden die Mapping-Information mittels Annotations implementiert, so wird eine
org.hibernate.cfg.AnnotationConfiguration benötigt. Diese Klasse erbt von
Configuration.
Die Konfiguration erfolgt nach demselben Konzept. Einziger Unterschied besteht beim
Einbinden von Entitäten.
In der Hibernate-Konfigurationsdatei werden Klassen weiterhin über das mappingElement eingebunden. Jedoch muss dabei der Parameter class mit dem Namen der
.java-Datei angegeben werden.
<mapping class="Gegenstand.java" />
Im Quellcode können Entitäten mittels der Methode addAnnotatedClass() eingebunden werden.
AnnotationConfiguration conf =
new AnnotationConfiguration().
addAnnotatedClass(Gegenstand.class).configure();
3.10 Sessions
3.10
52
Sessions
3.10.1 Übersicht
Hibernate-Sessions stellen die Schnittstelle zur Datenbank dar. Sie werden in Objekten vom Typ org.hibernate.Session repräsentiert. Mithilfe dieser Objekte werden
alle Datenbankzugriffe realisiert.
Intern verwendet eine Hibernate-Session eine org.hibernate.Connection.
Connection repräsentiert eine JDBC-Verbindung. Dies wird im Kapitel 3.11 beschrieben.
Auf Datenbankseite wird für jede dieser Verbindungen eine Datenbank-Session geöffnet, welche Ressourcen verbraucht. Sessions sollten daher so kurz als möglich offen
gehalten werden. Mehr Informationen dazu gibt es im Kapitel 4.
Um eine Hibernate-Session zu erzeugen, sind nachstehende Schritte erforderlich:
1. Erzeugen eines Configuration-Objekts
Im Kapitel 3.9 wurde beschrieben, wie ein Configuration-Objekt erzeugt werden kann.
Configuration configuration =
new Configuration().configure();
2. SessionFactory erzeugen
Configuration stellt die Mehode buildSessionFactory() zur Erzeugung
einer SessionFactory zu Verfügung. SessionFactory-Objekte sind immutable. Nachträgliche Änderungen an der Konfiguration haben keine Auswirkung
auf eine bereits instanzierte SessionFactory.
SessionFactory sessionFactory =
configuration.buildSessionFactory();
3. Session erzeugen
SessionFactory stellt die Methode openSession() zur Verfügung. Diese
gibt ein Session-Objekt zurück.
Session session = sessionFactory.openSession();
3.10 Sessions
53
Das Configuration-Objekt wird nach dem Erzeugen einer SessionFactory nicht
mehr benötigt. Es stellt sozusagen ein Wegwerfobjekt“ dar. Sinnvollerweise wird die
”
Referenz configuration danach gelöscht, um es bereit für den Garbage-Collector
zu machen.
Der Umweg“ zum Erzeugen einer Session über die SessionFactory wirkt zunächst
”
überflüssig.
Ein SessionFactory-Objekt wird unter anderem für das Second-Level-Caching
benötigt. Mehr Informationen dazu gibt es im Kapitel 3.14.
Des Weiteren macht es Sinn, ein SessionFactory-Objekt in der Applikation zu teilen. Dadurch wird bei jedem Datenbankzugriff Aufwand für die Erzeugung einer
Configuration und SessionFactory eingespart.
Session hat folgende Methoden zum Laden, Speichern und Löschen von Objekten:
Methode
save(Object o)
update(Object o)
saveOrUpdate(Object o)
get(Class c, Serializable id)
load(Class c, Serializable id)
clear()
close()
createQuery()
createSQLQuery
flush()
delete(Object o)
Aufgabe
Entität speichern
Verändern einer Entität
Ruft save(o) oder update(o) auf
Laden einer Entität aus der Datenbank
Laden einer Entität aus der Datenbank
Session-Level-Cache leeren
JDBC-Verbindung schließen und Ressourcen freigeben
Erzeugen einer Query für ein HQL-Statement
Erzeugen einer Query für ein SQL-Statement
Änderungen am Session-Level-Cache persistieren
Löschen einer Entität
Tabelle 3.1: Wichtige Session Methoden
Der Unterschied zwischen den Methoden get() und load() liegt in der Behandlung
nicht existierender Entitäten. Während get() null zurückliefert wenn die Entität nicht
existiert, wirft load() eine Exception.
Die Methoden createQuery() bzw. createSQLQuery() werden im Kapitel 3.13
näher erklärt.
3.10 Sessions
3.10.2
54
Session-Level-Cache
Eine Hibernate-Session verwendet einen einfachen Cache. Dieser beschränkt sich auf
die Lebenszeit der Session, an der er hängt. Dieser Cache wird als Session-LevelCache oder First-Level-Cache bezeichnet. Wird eine Entität verändert, neu erzeugt
oder gelöscht, geschehen diese Änderungen zunächst im Session-Level-Cache. Dadurch wird die Anzahl der Datenbankzugriffe reduziert.
Hibernate verwaltet diesen Cache selbstständig, sodass sich AnwendungsentwicklerInnen kaum darum kümmern müssen.
Werden Objekte manipuliert, geschieht dies zunächst im Session-Level-Cache. Wird
ein Objekt aus der Datenbank geladen, so steht dieses Objekt im Cache. Bei jedem
Lesevorgang aus der Datenbank wird zuvor geprüft, ob es dieses Objekt bereits im
Cache gibt. Sollte dies der Fall sein, ist kein zusätzlicher Datenbankzugiff nötig.
Wie bereits erwähnt, ist der Session-Level-Cache an eine Session gebunden. Um den
Cache zu leeren dient die Methode session.clear(). Soll ein bestimmtes Objekt
vom Cache gelöst werden, gibt es session.evict(Object o). Mit session.flush()
wird der Zustand der Entitäten am Cache auf die Datenbank geschrieben.
3.10.3
Lebenszyklus von Entitäten
Der Zustand einer Hibernate-Entität ist nicht immer synchron mit dem Zustand in der
Datenbank. Wie bereits erwähnt, werden Entitäten an die Session gebunden, welche diese Objekte wiederum im Session-Level-Cache verwaltet. Die Session verwaltet
zusätzlich den Zustand der Objekte.
Es gibt drei mögliche Zustände, in denen sich eine Hibernate-Entität befinden kann:
• Transient
Es existiert (noch) keine Datenbankrepräsentation der Entität. Die Entität ist an
keine Session gebunden.
• Persistent
Die Entität ist an eine Session gebunden und hat einen Primärschlüssel. Änderungen der Entität wirken sich auf den Session-Level-Cache aus. Erst bei einem
flush des Caches wird die Entität auf die Datenbank geschrieben. Daher kann
der Zustand einer persistenten Entität vom Datenbankzustand abweichen.
3.11 Connections
55
• Detached
Die Entität wird von der Session losgelöst (engl. detached). Da die Entität vom
Zustand Persistent in den Zustand Detached übergegangen ist, besitzt sie einen
Primärschlüssel.
Abbildung 3.10: Lebenszyklus von Entitaeten
3.11
Connections
Wie im vorangegangenen Kapitel beschrieben, verwendet eine Hibernate-Session eine org.hibernate.Connection.
Die Erzeugung eines Connection-Objekts ist sehr zeitaufwändig.
Zu diesem Zweck gibt es die Möglichkeit, Connections auf Vorrat anzulegen. Dies ist
mithilfe eines Connection-Pools möglich.
3.11.1
Connection-Pool
Wird eine Connection gebraucht, wird sie aus diesem Pool genommen. Benutzte Connections werden wieder zurückgelegt.
Hibernate bietet selbst eine Connection-Pool-Implementierung an. Dieser Pool wird
per-Default von Hibernate-Applikationen verwendet. Die Größe des Pools wird standardmäßig mit 20 initialisiert. Sie kann mittels der Property
3.11 Connections
56
hibernate.connection.pool\_size verändert werden.
Der Hibernate-Connection-Pool ist nur für Testzwecke konzipiert. Er ist unperformant
und nicht weit entwickelt. Deshalb sollte er im laufenden Betrieb einer Applikation nicht
eingesetzt werden.
Eine wesentlich bessere Implementierung dieser Technologie wird bereits mit dem
Hibernate-Core mitgeliefert. Dabei handelt es sich um den open-source ConnectionPool c3p0. Dieser ist sehr einfach zu konfigurieren. In der Hibernate-Konfugiration
muss lediglich eine hibernate.c3p0-Property definiert werden.
Z.B. hibernate.c3p0.max_size=20. Der c3p0-Pool bietet eine Fülle an Konfigurationsmöglichkeiten.
Die Verwendung eines solchen Connection-Pools ist für viele Anwendungen bereits
ausreichend.
3.11.2
JNDI
Läuft die Anwendung in einem Application-Server, ist es in den meisten Fällen besser,
die Connections über JNDI6 zu beziehen.
JNDI ist Teil der Java Plattform und stellt mehrere Namens- und Verzeichnisdienste für
Applikationen zur Verfügung.
Ein Application-Server arbeitet in einem Computernetwerk. Auf ihm werden Client/ServerAnwendungen ausgeführt. Dies kann in einem internen Netz (Lan), sowie im Internet
geschehen. Bei einem Webserver stellt der Browser den Client dar.
Um Connections über JNDI vom Anwenungsserver zu beziehen, dient
javax.sql.Datasource.
Nachfolgende Tabelle zeigt wichtige Konigurations-Properties:
Name der Property
hibernate.connection.datasource
hibernate.jndi.url
hibernate.jndi.class
Zweck
Name der Datasource JNDI
URL des JNDI-Providers
Klasse der JNDI-InitialContextFactory
Tabelle 3.2: Konfiguration der JNDI-Datasource
6
Java Naming and Directory Interface
3.12 Transaktionen
3.12
57
Transaktionen
3.12.1 Übersicht
Um Änderungen, die auf dem Session-Level-Cache durchgeführt wurden auch auf
die Datenbank zu übertragen, dienen Transaktionen. Eine Transaktion bezeichnet die
Summe mehrerer logischer Operationen auf der Datenbank.
Transaktionen folgen dem ACID-Prinzip:
• Atomicity (Atomarität)
Transaktionen werden entweder ganz, oder gar nicht durchgeführt. Sie sind unteilbar.
• Consistency (Konsistenz)
Der Datenbestand ist vor und nach der Transaktion konsistent.
• Isolation (Isolation)
Gleichzeitig ausgeführte Transaktionen dürfen sich gegenseitig nicht stören.
• Durability (Dauerhauftigkeit)
Änderungen einer Transaktion an den Daten müssen dauerhaft bestehen bleiben. Geschachtelte Transaktionen sind daher abzulehnen. Bei einem rollback
einer übergeordneten Transaktion würde auch die Subtransaktion zurückgerollt.
Dies stellt einen Verstoß gegen dieses Prinzip dar.
Session stellt die Methode beginTransaction() bereit. Dadurch wird ein Objekt
vom Typ org.hibernate.Transaction instanziert. Dies stellt gleichzeitig den Beginn der Transaktion dar. Geschachtelte Transaktionen werden nicht unterstützt.
Transaktionen werden mittels folgender Methoden beendet:
• commit()
Ruft die Methode flush() der Session auf und schreibt so die Änderungen in
die Datenbank. Danach wird ein Datenbank-Commit durchgeführt.
• rollback()
Bei einem Fehlerfall wird die Methode rollback() verwendet. Diese macht alle
Änderungen, die in der Transaktion durchgeführt wurden, rückgängig.
3.12 Transaktionen
58
Im nachstehenden Beispiel wird zunächst ein Gegenstand-Objekt geladen, anschließend verändert und die Änderungen auf die Datenbank geschrieben.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package beans;
import
import
import
import
import
model.Gegenstand;
org.hibernate.HibernateException;
org.hibernate.Session;
org.hibernate.Transaction;
org.hibernate.cfg.Configuration;
public class GegenstandBean {
public void changeGegenstand(int pk, String kurzBezNeu) {
Gegenstand aktGegenstand;
Session session;
Transaction transaction;
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 }
session = new Configuration().configure().
buildSessionFactory().openSession();
transaction = session.beginTransaction();
←-
try {
aktGegenstand = (Gegenstand)
session.load(Gegenstand.class, pk);
aktGegenstand.setGegKurzbez(kurzBezNeu);
transaction.commit();
} catch (HibernateException he) {
transaction.rollback();
//Weitere Fehlerbehandlung
} finally {
session.close();
}
}
Listing 3.20: Session und Transaktionen
In Zeile 16 wird eine neue Session erzeugt. Diese öffnet in Zeile 17 eine Transaktion.
Im try-Block wird dann mittels load() ein Gegenstands-Objekt mit dem PrimaryKey pk in den First-Level-Cache des session-Objekts geladen. Sollte der Schlüssel
pk in der Datenbank nicht existieren, wird eine HibernateException geworfen. Anschließend wird der Gegenstand verändert und in Zeile 22 wird diese Änderung in
die Datenbank geschrieben.
Im finally-Block wird die Session geschlossen. Der finally-Block wird, unabhängig
3.12 Transaktionen
59
davon, ob eine Exception auftritt, abgearbeitet. Eine geöffnete Session sollte auf jeden
Fall wieder geschlossen werden. Deshalb wird die in Listing 3.20 gezeigte Form der
Implementierung vorgeschlagen.
Nach dem Aufruf von commit() oder rollback() kann die Transaktion mittels
begin() wiederverwendet werden.
Standardmäßig verwendet Hibernate intern eine JDBCTransaction. Alternativ kann
auch JTA7 verwendet werden. JTA erlaubt es, den Transaktionsmechanismus eines
Anwendungsservers zu verwenden.
3.12.2
Konkurrierende Datenbankzugriffe
Laufen mehrere Transaktionen zeitgleich nebeneinander, kann es zu Fehlern kommen.
Hierbei sind zwei Fehlertypen zu unterscheiden.
Einerseits sind das Transaktionen, die sich bei paralleler Ausführung gegenseitig stören.
Im speziellen betrifft dies beispielsweise das unkommitierte Lesen von Änderungen.
Andererseits logische Fehler, bei denen Transaktionen Änderungen verursachen, die
bereits festgeschriebene Daten ungewollt überschreiben.
Insgesamt können nachstehende 4 Fehler auftreten:
• Lost Update
• Dirty Read
• Nonrepeatable Read
• Phantom Read
Der Lost Update stellt einen logischen Datenhaltungsfehler dar. Dies wird im Kapitel
3.12.4 behandelt. Die drei weiteren Fehler Dirty Read, Nonrepeatable-Read und Phantom Read betreffen die Transaktionsisolation.
7
Java Transaction API
3.12 Transaktionen
60
Lost Update
Zwei Benutzer lesen zur selben Zeit den selben Datensatz aus. Die dafür nötigen beiden Transaktionen sehen dabei einander nicht. Nach der Bearbeitungszeit schreibt ein
Benutzer zuerst in die Datenbank und kommitiert diese Änderungen. Kurz darauf kommitiert auch der zweite Benutzer und überschreibt somit die Änderungen des ersten
Benutzers. Somit geht das Update verloren (engl. lost).
Benutzer A
Liest Datensatz 0815
Formular zum Bearbeiten des Datensatzes 0815 wird am Bildschirm angezeigt
Schreibt Datensatz 0815 geändert in
die Datenbank
Benutzer B
Liest Datensatz 0815
Formular zum Bearbeiten des Datensatzes 0815 wird am Bildschirm angezeigt
Schreibt Datensatz 0815 geändert in
die Datenbank und überschreibt somit
die Änderungen von Benutzer A
Tabelle 3.3: Logisches Lost Update Problem
Dirty Read
Tritt dann auf, wenn eine Transaktion Daten liest, die von einer anderen Transaktion
geschrieben, jedoch nicht kommitiert wurden. Wird nach dem Lesen ein rollback der
anderen Transaktion durchgeführt, wird mit schmutzigen (engl. dirty) Daten gearbeitet.
Transaktion A
Transaktion B
Schreibt Datensatz 0815 in die Datenbank, kommitiert jedoch nicht
Liest den unkommitierten Datensatz
0815
Transaktion wird mit Rollback zurückgerollt
Transaktion B arbeitet mit einem
schmutzigen Wert weiter
Tabelle 3.4: Dirty Read
3.12 Transaktionen
61
Nonrepeatable Read
Mehrere Lesevorgänge auf denselben Datensatz innerhalb einer Transaktion liefern
unterschiedliche Werte. Der Lesevorgang ist nicht wiederholbar (engl. not repeatable).
Transaktion A
Liest Datensatz 0815
Transaktion B
Verändert Datensatz 0815
Kommitiert die Änderung
Liest Datensatz 0815 und erhält einen
anderen Datensatz als beim ersten Lesevorgang
Tabelle 3.5: Nonrepeatable Read
Phantom Read
Dieses Problem ist eine Besonderheit des Nonrepeatable-Read. Der Fehler tritt bei
einer Menge von Datensätzen in Zusammenhang mit Aggregatfunktionen auf.
Transaktion A
Transaktion B
Liest die Summe aller Gegenstände, die
auf Deutsch unterrichtet werden. Ergebnis: 44.
Verändert den Gegenstand Geschichte.
Dieser wird nun in Englisch unterrichtet.
Kommitiert die Änderung.
Liest wieder die Summe aller Gegenstände, die auf Deutsch unterrichtet
werden und erhält den Wert 43.
Tabelle 3.6: Phantom Read
3.12.3
Transaktionsisolation
Um den Problemen konkurrierender Transaktionen zu begegnen, gibt es auf Seiten
der Datenbank den Isolationsgrad.
3.12 Transaktionen
62
Hibernate bietet hier die im ANSI-SQL92 Standard festgeschriebenen Isolationslevel
an:
• Read Uncommitted
Bei diesem Isolationslevel findet praktisch keine Isolation statt. Bearbeitet eine
Transaktion einen bestimmten Datensatz, sind diese Änderungen sofort für andere Transaktionen sichtbar. Auch wenn diese nicht kommitiert wurden.
• Read Committed
Änderungen einer Transaktion werden erst nach dem Kommitieren für andere
Transaktionen sichtbar. Dieser Isolationslevel ist Standardverhalten vieler RDBMS,
da er einen guten Kompromiss zwischen Sicherheit und Performance darstellt.
• Repeatable Read
Diese Strategie stellt sicher, dass mehrere Lesevorgänge auf denselben Datensatz innerhalb der Transaktion jeweils das selbe Ergebnis liefern. Das bedeutet,
die Transaktion arbeitet mit jenem Datenbestand, der zu Beginn vorhanden war.
• Serializable
Der höchste Isolationsgrad. Parallele Transaktionen laufen vollkommen isoliert
voneinander. Vereinfacht gesagt, wird vorgetäuscht, dass alle Transaktionen sequentiell hintereinander abgearbeitet werden. Aus Performancegründen ist dies
bei den meisten RDBMS nicht der Fall. Meist werden am Beginn einer Transaktionen alle betroffenen Tabellen gesperrt.
Bei dieser Strategie kommmt es relativ häufig zu Abbrüchen von Transaktionen.
Die Applikation muss gegebenfalls auf diese reagieren können und die Transaktion neu starten.
Folgende Tabelle gibt eine Übersicht über die Möglichkeit von Datenbankfehlern bei
Wahl des jeweiligen Isolationsgrades.
Isolationsgrad
Dirty Read
Read Uncommitted
Read Committed
Repeatable Read
Serializable
möglich
unmöglich
unmöglich
unmöglich
Nonrepeatable
Read
möglich
möglich
unmöglich
unmöglich
Phantom
Read
möglich
möglich
möglich
unmöglich
Tabelle 3.7: Fehlerfaelle der Isolationsgrade
Allgemein gilt, je höher der Isolationslevel, desto niedriger die Performance und umgekehrt.
3.12 Transaktionen
63
Welcher Isolationslevel gewählt wird, hängt in erster Linie von den Anforderungen der
Applikation ab.
Mittels der Property hibernate.connection.Isolation kann der Transaktionsisolationsgrad bestimmt werden. In der Klasse java.sql.Connection stehen diese
Strategien in Form einer Enumeration bereit.
new Configuration().configure().
setProperty("hibernate.connection.isolation",
java.sql.Connection.TRANSACTION_REPEATABLE_READ)
Die Isolationslevel werden je nach verwendetem Datenbankmanagementsystem auf
dessen Isolationsmechanismus umgelegt.
Es kann jedoch sein, dass das Datenbanksystem nicht alle Isolationslevel unterstützt
oder andere verwendet. Beispielsweise wird bei Oracle das Read Uncommited und
Repeatable Read nicht unterstützt, dafür zusätzlich Read Only angeboten.
Daher ist es wichtig, sich mit den Konzepten der Transaktionsisolation der verwendeten Datenbank auseinanderzusetzen.
3.12.4
Sperrstrategien
Im Kapitel 3.12 wurde beschrieben, wie die technischen Datenbankfehler gehandhabt
werden. Dieses Kapitel bezieht sich auf die logischen Fehler.
Weiter oben wurde das Lost Update Problem erklärt.
Grundsätzlich wäre es möglich, dieses Problem durch Lange Transaktionen zu umgehen. Bei langen Transaktionen wird das Laden und Speichern eines Datensatzes in
einer Transaktion durchgeführt. Durch entsprechend hohe Transaktionsisolation würde
das Problem nicht mehr auftreten. Jedoch kann keine Aussage darüber gemacht werden, wie lange BenutzerInnen zum Manipulieren der Daten brauchen. Lange Transaktionen wirken sich negativ auf die Performance aus. Deshalb ist diese Strategie abzulehnen.
Standardmäßig arbeitet Hibernate nach dem Prinzip Wer zuerst kommt, malt zuerst,
”
und wird zuerst überschrieben“. Spätere Updates übeschreiben frühere. Hier wird ersichtlich, das Hibernate ohne entsprechende Konfiguration nichts gegen den Lost Update unternimmt.
3.12 Transaktionen
64
Zu diesem Zweck gibt es Sperren (engl. Locks). Dabei gibt es optimistische und pessimistische Sperrstrategien.
Pessimistische Sperrstrategie
Bei dieser Strategie wird von häufig auftretenden Kollisionen ausgegangen.
Hier wird auf Ebene von Datensatz bzw. Objekt gesperrt, indem sogenannte Locks
gesetzt werden.
Benutzer A, der den Datensatz bearbeiten will, setzt die Sperre und gibt sie nach Abschluss seiner Arbeiten wieder frei. Sollte Benutzer B innerhalb dieser Bearbeitungszeit ebenfalls auf diesen Datensatz zugreifen, kann er ihn zwar lesen, wird jedoch
darüber informiert, dass es nicht möglich ist, den Datensatz zu verändern, bis Benutzer A die Sperre wieder freigibt.
Vorteil dieser Strategie ist, dass keine Arbeit verloren geht. Da jeder Benutzer über
etwaige Sperren informiert wird. Nachteilig ist der Umstand, dass z.B. bei einem Systemabsturz von Benutzer A die Sperre weiterhin gehalten wird und Benutzer B mitunter
lange darauf warten muss, bis er die Sperre erhält.
Hibernate erlaubt folgende Lock-Modi:
• LockMode.NONE
• LockMode.READ
• LockMode.UPGRADE
• LockMode.UPGRADE_NOWAIT
• LockMode.WRITE
• LockMode.FORCE
Alle Lock-Modi bleiben nur innerhalb einer Transaktion in Kraft.
Häufig ist es jedoch nötig, länger andauernde Sperren zu halten. Eine mögliche Variante dies zu bewerkstelligen ist, in den entsprechenden Tabelle zusätzliche Spalten
anzulegen, welche Informationen zu etwaigen Sperren beinhalten.
3.13 Datenbankabfragen
65
Hibernate setzt die pessimistische Sperrstrategie über Datenbankmechanismen wie
beispielsweise SELECT FOR UPDATE um.
Optimistische Sperrstrategie
Diese Strategie ist sinnvoll, wenn Kollisionen selten auftreten. Es wird gänzlich ohne
Locks gearbeitet.
BenutzerInnen können zu jeder Zeit Daten einladen. Dabei wird man nicht darüber
infomiert, ob jemand Anderer ebenfalls darauf zugreift. Erst beim Speichern wird dieser Umstand geprüft. Zu diesem Zweck wird beim Einlesen die Versionsnummer des
Datensatzes mitgelesen. Beim Speichern wird diese dann überprüft. Sollte sich diese
Zahl verändert haben, wird der Benutzer darüber informiert und der Speichervorgang
wird abgebrochen.
Nachteil dieser Strategie ist, dass Arbeit verloren gehen kann. Vorteil ist die bessere
Performance als bei der pessimistischen Sperrstrategie.
Bei der Implementierung dieser Strategie, muss die Versionsnummer eine eigene Instanzvariable der Entität sein. Dabei muss es sich um eine Integer-Instanz handeln.
Im Mapping muss dieses Attribut mittels des <version>-Tags deklariert werden.
3.13
Datenbankabfragen
3.13.1 Übersicht
Neben den vordefinierten Zugriffsmethoden von Sessions können auch selbst formulierte Statements an die Datenbank gesendet werden. Dies ist nötig, wenn Entitäten
nach bestimmten Mustern gesucht sind. Im Speziellen dann, wenn der Primärschlüssel
einer Entität nicht bekannt ist.
Dies kann in Form von HQL- und normalen SQL Statements, sowie mithilfe von Criterias erfolgen.
3.13 Datenbankabfragen
3.13.2
66
Hibernate Qery Language (HQL)
HQL ist eine objektorientierte Zugriffssprache, welche stark an die Syntax von normalen SQL-Statements angelehnt ist.
HQL-Statements sind ausschließlich für Entitäten (d.h. gemappte Java-Klassen) anwendbar, welche in der Configuration eingebunden sind.
Die Schlüsselwörter wie beispielsweise from oder insert sind nicht Case-Sensitiv.
Bei SQL-Statements hat es sich etabliert, Schlüsselwörter groß zu schreiben. Da Tabellenund Spaltenbezeichnungen auf Datenbankseite meist klein geschrieben werden bringt
dies einen großen Vorteil in der Lesbarkeit.
HQL-Statements werden mit den Bezeichnungen der Klassen und deren Instanzvariablen formuliert.
Bezeichnungen dieser Klassen bzw. Variablen werden nach dem JavaBean-Standard
vergeben. Dadurch beginnt der Name von Java-Klassen mit einem Großbuchstaben.
Deshalb bietet es sich eher an, HQL-Statements mit kleingeschriebenen Schlüsselwörtern zu formulieren.
Die Bezeichnungen der Variablen und Klassen sind Case-Sensitiv.
Wie im Kapitel 3.10 beschrieben, besitzt ein Objekt vom Typ Session die Methode
createQuery(String hql). Diese Methode liefert eine org.hibernate.Query
zurück.
Query q = session.createQuery("from Gegenstand");
Mit einer Query ist es möglich, HQL-Statements auszuführen.
Abfragen
Zum Ausführen einer Abfrage dienen folgende Methoden:
• list()
• iterator()
• scroll()
3.13 Datenbankabfragen
67
• uniqueResult()
Am häufigsten werden die Methoden list() und uniqueResult() verwendet.
list() liefert eine java.util.List mit den Ergebnissen der Suchanfrage.
uniqueResult() wird eingesetzt, wenn die Abfrage genau einen Datensatz liefern
soll. Sollte kein Datensatz den Suchkriterien entsprechen, wird null zurückgegeben.
Wird mehr als ein Datensatz gefunden, wird eine HibernateException geworfen.
In einem HQL-Statement können wie oben erwähnt nur die Bezeichnungen der JavaKlassen und deren Instanzvariablen verwendet werden. Diese Java-Klassen müssen
gemappt sein.
Es ist egal, ob man den voll qualifizierten Namen oder nur den Klassennamen angibt.
from Gegenstand
ist gleichbedeutend mit
from model.Gegenstand
Diese beiden Abfragen liefern alle Datensätze der Tabelle gegenstand.
Mit as kann innerhalb der Abfrage ein anderer Name vergeben werden.
from Gegenstand as geg
Das Schlüsselwort as ist optional.
from Gegenstand geg
Will man die Abfrage einschränken, gibt es äuquivalent zu normalem SQL die whereKlausel.
from Gegenstand geg where geg.gegLangbez = ’Projektentwicklung’
Abfragen können mit order by sortiert und mit group by gruppiert werden. Diese
beiden Mechanismen entsprechen exakt jenen von SQL.
3.13 Datenbankabfragen
68
Query q = session.createQuery("‘
from Gegenstand geg
group by geg.muttersprache
order by geg.gegKurzbez asc"’);
Die optionalen Parameter asc und desc beim order-by geben die Sortierreihenfolge
an. Durch group by wird bei Ausführung dieses Statements eine List<Object[]>
zurückgeliefert.
List<Object[]> gruppiert = q.list();
Select-Abfragen
Mithilfe des Schlüsselwortes select kann das Ergebnis einer Abfrage bestimmt werden. Es kann angegeben werden, welche Objekte oder Attribute zurückgeliefert werden sollen.
select gegId, gegMspId
from Gegenstand
Es ist auch möglich, typsichere Java-Objekte als Ergebnistyp zu definieren.
select new GegenstandKlein(gegId, gegKurzBez, gegMspId)
from Gegenstand
Im obigen Beispiel muss es eine Klasse geben, welche einen Konstruktor mit dieser
Schnittstelle besitzt.
Polymorphe-Abfragen
Wie im Kapitel 3.8 gezeigt, ist es möglich, Vererbungshierarchien zu mappen. Mithilfe
von HQL sind polymorphe Abfragen möglich.
from Gegenstand
3.13 Datenbankabfragen
69
Diese Abfrage liefert nicht nur Gegenstand-Objekte, sondern auch Objekte der Subklassen. Bei der im Kapitel 3.8 gezeigten Vererbungshirarchie werden so auch Instanzen von FachpraktischerGegenstand und TheoretischerGegenstand geliefert.
from java.lang.Object
Diese Abfrage liefert als Ergebnis alle Entitäten und somit jeden Datensatz der Datenbank.
HQL-Expressions
Folgende Tabelle gibt eine Übersicht über wichtige HQL-Expressions.
Gruppe
Mathematische Operatoren
Vergleichsoperatoren
Logische Operatoren
Abfrageoperatoren
Stringmanipulation
Zeit und Datum
Umgang mit Collections
Syntax
+, -, *, /, abs(), sqrt(),
bit_length(), mod()
=, >=, <=, <>, !=, like
and, or, not
in, not in, between, is null,
is not null, is empty,
is not empty, member of,
not member of
concat(), substring(), str()
trim(), lower(), upper(),
length(), locate(), abs(),
sqrt(), bit_length(), mod()
current_date(), current_time(),
current_timestamp(), second(),
minute(), hour(), day(),
month(), year()
size(), min_element(),
max_element(), minindex(),
maxindex()
Tabelle 3.8: Wichtige HQL Expressions
3.13 Datenbankabfragen
70
In-Parameter
In-Parameter können wie nachfolgend angegeben eingefügt werden:
• ?
Diese Form ist ähnlich der JDBC-Schreibweise. Einziger Unterschied besteht in
der Nummerierung der Parameter.
JDBC beginnt bei der Nummerierung mit 1. Hibernate hingegen fängt bei 0 an.
Query q = session.createQuery("from Gegenstand geg
where geg.gegLangbez = ?");
• :name
Doppelpunkt gefolgt vom Namen des Parameters. Dadurch kann das Statement
übersichtlicher gestaltet werden. Des Weiteren entfällt die umständliche Nummerierung.
Query q = session.createQuery("from Gegenstand geg
where geg.gegLangbez = :gegenstandsname");
Zum Setzen der Parameter gibt es Methoden für die entsprechenden Datentypen.
Hibernate liefert zwei überladene Methoden zu jedem Datentyp:
Eine davon bekommt an erster Stelle einen Integer übergeben. Damit werden die Parameter, welche in Form eines Fragezeichens angegeben wurden, gesetzt.
q.setString(0, "Programmieren");
Die zweite bekommt einen String übergeben, der den Namen des Parameters enthält.
Der Doppelpunkt wird nicht angegeben.
q.setString("gegenstandsname", "Programmieren");
In HQL-Statements ist das Vergleichen ganzer Entitäten möglich. Dazu dient die Methode setEntitiy.
Query q = session.createQuery("from Gegenstand geg
where geg.mutterprache = :muttersprache");
q.setEntity("muttersprache", muttersprache);
3.13 Datenbankabfragen
71
Im Kapitel Objekt-Relationaler Bruch wurde beschrieben, dass Tabellen keine Datentypen sind.
Deshalb ist es in der relationalen Welt auch nicht möglich, einen Vergleich zweier Tabellen in dieser Form durchzuführen. Hier muss auf den Primärschlüssel der Tabelle
geprüft werden. Hibernate setzt die objektorientierte Denkweise von HQL-Statements
auf die relationale Denkweise um.
Im Speziellen heißt das, dass in der oben gezeigten where-Klausel
Fremd- und Primärschlüssel verglichen werden.
Anhand dieses Beispiels wird die arbeitsweise des objektorientierten HQL ersichtlich.
Joins
Hibernate unterstützt die in ANSI8 -SQL 92 standardisierten Join-Typen. Diese sind in
Abbildung 3.11 ersichtlich.
Abbildung 3.11: HQL - Joins
Verknüpfungen (engl. joins) werden großteils implizit verwendet. Es sind aber auch
explizite Joins möglich.
Hibernate liefert bei einem expliziten Join immer Objektfelder, welche die beteiligten
Entitäten enthalten. Dabei wird die Reihenfolge der Objekte in einem Objektfeld von
8
American National Standards Institute
3.13 Datenbankabfragen
72
der Abfolge der Entitäten im HQL-Statement bestimmt.
Folgendes Listing zeigt einen expliziten join. Es werden alle Gegenstände ausgegeben, die in der Sprache Deutsch oder Englisch unterrichtet werden.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Query q = session.createQuery(
"from Gegenstand geg " +
"inner join geg.muttersprache Muttersprache " +
"with geg.muttersprache.mspLangbez = :sprache1 " +
"or geg.muttersprache.mspLangbez = :sprache2");
q.setString("sprache1", "Deutsch");
q.setString("sprache2", "Englisch");
List<Object[]> l = q.list();
for (Object [] obj : l) {
Gegenstand geg = (Gegenstand) obj[0];
Muttersprache msp = (Muttersprache) obj[1];
System.out.println(geg.getGegLangbez()
+ " - " + msp.getMspLangbez());
}
Listing 3.21: Expliziter HQL-Join
Die In-Parameter werden in den Zeilen 7 und 8 gesetzt.
In diesem Beispiel wird in Zeile 9 eine Liste von Objektfeldern erzeugt. An erster Stelle (Index 0) eines Objektfeldes wird jeweils ein Gegenstand-Objekt geliefert und an
zweiter Stelle (Index 1) ein Muttersprache-Objekt.
Beim impliziten Join wird das Schlüsselwort join nicht verwendet. Stattdessen wird
mithilfe des Punktoperators auf die jeweilige Entität zugegriffen. Dabei muss im GegenstandMapping eine Beziehung zur Muttersprache vorhanden sein.
Beim impliziten Join verwendet Hibernate den inner join.
from Gegenstand geg
where geg.muttersprache.mspLangbez = ’Englisch’
3.13 Datenbankabfragen
73
Updates und Deletes
Seit Hibernate 3 ist es möglich, mithilfe von HQL Objekte in der Datenbank zu manipulieren.
• Update
update Gegenstand geg
set geg.gegLangbez = ’Englisch’
where geg.gegId = 3
Wie in Kapitel 3.12.2 beschrieben, wird für die optimistische Sperrstrategie eine Versionsnummer benötigt. Mithilfe der Schlüsselwörter update versioned
wird die Versionsnummer hochgezählt.
• Delete
delete Gegenstand
where geg.gegId = 3
Die gelöschten bzw. veränderten Objekte müssen vorher nicht geladen werden. Änderungen werden mittels der Methode executeUpdate() durchgeführt. Diese Methode
liefert die Anzahl der veränderten Datensätze zurück.
3.13.3
Criteria
Abfragen können neben HQL auch mittels Criteria formuliert werden. Diese Abfragen
richten sich an genau einen Entitätstypen. Der Vorteil dieser Technik ist, dass keine
HQL-Kenntnisse benötigt werden. Des Weiteren wird Typsicherheit garantiert. Jedoch
ist es nicht möglich, Joins zu verwenden.
Um Abfragen zu formulieren dient ein Objekt vom Typ org.hibernate.Criteria.
Zum Erzeugen eines Criteria-Objekts liefert Session die Methode
createCriteria(). Dieser Methode wird die gewünschte Klasse übergeben.
Ausgeführt wird eine Criteria mit äuqivalenten Methoden zu HQL. Einzig die Methode iterate gibt es nicht.
Criteria criteria = s.createCriteria(Gegenstand.class);
List<Gegenstand> gegenstaende = criteria.list();
3.13 Datenbankabfragen
74
Diese Abfrage liefert alle Entitäten der Gegenstand-Tabelle. Um Abfragen einzuschränken
gibt es die Klasse org.hibernate.criterion.Restrictions. Diese Einschränkungen (engl. Restrictions) können zu einer Criteria mittels der Methode
add(Criterion c) hinzugefügt werden.
Die Klasse Restrictions liefert dazu eine Vielzahl an statischen Methoden, welche
alle ein Criterion-Objekt zurückliefern.
Mithilfe der Methode setMaxResults(int maxResults) ist es möglich, die Anzahl
der zurückgelieferten Objekte einzuschränken.
criteria.add(Restrictions.between("gegId", 1, 22));
criteria.setMaxResults(10);
Die Methode add liefert immer ein Criteria-Objekt zurück. Dadurch kann dieser
Ausdruck wesentlich übersichtlicher gestaltet werden. Des Weiteren ist es seit Java
5 möglich, statische Imports zu verwenden. Dadurch können die Methoden der Klasse Restrictions, welche ja alle statisch sind, nach dem Einbinden ohne Angabe des
Klassennamens verwendet werden.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import
import
import
import
java.util.List;
org.hibernate.Session;
org.hibernate.cfg.Configuration;
static org.hibernate.criterion.Restrictions.*;
public class GegenstandBean(){
public void getSomeGegenstaende(){
Session session = new Configuration().configure()
.buildSessionFactory().openSession();
List<Gegenstand> gegenstaende =
session.createCriteria(Gegenstand.class)
.add(between("gegId", 1, 22))
.add(isNotEmpty("gegNotenattrArt"))
.setMaxResults(10)
.list();
return gegenstaende;
}
}
Listing 3.22: Datenbankabfrage mit Criteria
3.14 Fetching und Caching
3.13.4
75
SQL
Es ist auch möglich, normale SQL-Statements im jeweils verwendeten Datenbankdialekt zu formulieren.
Das kann unter anderem dann sinnvoll sein, wenn datenbankoptimierte Abfragen definiert werden müssen.
Um ein SQL-Statement zu benutzen, dient das Interface SQLQuery, welches mit dem
Aufruf der Methode createSQLQuery() an einer Session erzeugt wird.
s.createSQLQuery("SELECT * FROM Gegenstand").list();
Zum Ausführen einer SQLQuery gibt es wie in HQL die äuqivalenten Methoden list(),
scroll(), iterate() und uniqueResult(). Bei obenstehender SQLQuery wird
eine List<Object[]> zurückgeliefert.
3.14
Fetching und Caching
3.14.1 Übersicht
Datenbankzugriffe stellen in vielen Applikationen den Flaschenhals hinsichtlich Performance dar. Insbesondere ist dies bei Webapplikationen der Fall.
Anforderung an die Applikation ist es, die Datenbankzugriffe auf ein Minimum zu beschränken und wirklich nur jene Daten zu laden, welche auch tatsächlich benötigt werden. Hibernate stellt zu diesem Zweck verschiedene Mechanismen zur Verfügung.
Fetching
Dieser Begriff bezieht sich darauf, wann und wieviel Daten zu einem Zeitpunkt geladen werden sollen. Grundsätzlich gibt es zwei Strategien: Lazy Loading und Eager
Loading.
Caching
Dabei geht es um das Puffern bestimmter Datensätze. Wird ein gesuchter Datensatz
im Cache gefunden, muss keine Datenbankabfrage ausgeführt werden.
3.14 Fetching und Caching
3.14.2
76
Lazy Loading
Hibernate setzt standardmäßig das faule Laden (engl. lazy loading) um.
Hat eine Entität Beziehungen zu anderen Entitäten, werden diese erst dann geladen,
wenn sie tatsächlich benötigt werden.
Hibernate verwendet dazu Proxies, mit denen die referenzierten Objekte bis zum tatsächlichen Einladen dargestellt werden. Diese Proxies sind Stellvertreterobjekte und haben
dieselbe Schnittstelle wie das eigentliche Objekt.
Wird schließlich auf das Objekt zugegriffen, werden diese Proxies durch echte Objekte
aus der Datenbank ersetzt.
Dabei ist zu beachten, dass beim Zugriff auf den Primärschlüssel nicht nachgeladen
wird. Erst beim Zugriff auf eine Nicht-Primärschlüssel-Spalte wird nachgeladen.
Zur Illustrierung referenziert die Entität Gegenstand die Entität Muttersprache.
Diese Assoziation entspricht, wie im Kapitel 3.7 gezeigt, dem many-to-one-Beziehungstyp.
Folgendes Listing verdeutlicht das Lazy Loading anhand dieses Beispiels.
1
2
3
4
5
6
7
8
Configuration c = new Configuration().configure();
SessionFactory sf = c.getSessionFactory();
Session session = sf.openSession();
Gegenstand geg = session.load(Gegenstand.class, 5);
int i = geg.muttersprache.getMspId();
String mspKurzBez = geg.getMuttersprache().getMspKurzBez();
session.close();
Listing 3.23: Lazy Loading
Hier wird in Zeile 5 ein Gegenstand geladen. Durch das Lazy Laoding wird für die referenzierte Entität Muttersprache zunächst ein Proxy verwendet.
In Zeile 6 wird auf ein Attribut der Muttersprache zugegriffen. Hier wird jedoch noch
nicht nachgeladen, da es sich bei diesem Attribut (mspId) um den Primärschlüssel
handelt. Erst in Zeile 7, beim Zugriff auf ein Nicht-Primärschlüssel-Attribut (mspKurzBez),
wird das komplette Muttersprache-Objekt aus der Datenbank geladen.
3.14 Fetching und Caching
77
Hibernate wendet das Lazy Loading nur bei Entitäten an. Werttypen werden immer
sofort mitgeladen.
Hibernate erlaubt es, dass Verhalten beim Nachladen von assoziierten Entitäten zu
verfeinern. Es gibt nachstehende vier Strategien:
• Select-Fetching
Diese Strategie ist das Standardverhalten von Hibernate. Wird auf einen Proxy
zugegriffen, wird nur die eine, dem Proxy zugehörige Entität, nachgeladen.
Ein Problem dieser Strategie liegt in dem Umstand, dass beim Einladen einer
Entität genau eine Datenbankabfrage (1 Select Statement) benötigt wird.
Stehen jedoch beispielseise weitere referenzierte Entitäten in einer Collection,
werden diese erst beim Zugriff darauf nachgeladen. Würde man 100 referenzierte Entitäten nachladen, würde dies bedeuten, dass auch 100 Abfragen (100
SELECT Statements) an die Datenbank gesendet werden.
Zum Einladen einer Entität wird 1 Datenbankzugriff benötigt. Zum Nachladen
aller assoziierten Entitäten werden n Datenbankzugriffe notwendig. Daher wird
dieses Problem als 1+n-Problem bezeichnet.
• Batch-Fetching
Hier wird eine Batchgröße definiert.
Wird auf eine Entität einer Collection zugegriffen, die noch nicht eingeladen wurde, wird diese selbst und die mit der Batchgröße eingestellte Anzahl an weiteren
Entitäten im selben SELECT-Statement mitgeladen. Es werden sozusagen Entitäten auf Vorrat“ geladen. Nachteil dieser Version kann sein, dass mitgeladene
”
Entitäten ähnlich dem Eager Loading nicht benötigt werden.
Bei dieser Strategie vermindert sich das 1+n-Problem auf das 1+n/Batchgröße“”
Problem.
• Join-Fetching
Bei dieser Variante werden alle assoziierten Entitäten durch Joins sofort und in
einem Select-Statement geladen. Dabei werden OUTER JOINS verwendet.
Problematisch wird diese Strategie beim Einladen von Entitäten mit mehreren
Collections (von Entitäten). Durch die Bildung des kartesischen Produkts bei
Joins können die Ergebnismengen sehr groß werden.
• Subselect-Fetching
Die Probleme der Bildung eines kartesischen Produktes durch Join-Fetching fallen hierbei weg. Dies wird duch ein Subselect bewerkstelligt. Zunächst wird die
Entität eingeladen. Anschließend, in einem zweiten Select-Statement, werden
die assoziierten Entitäten geladen. Dieser zweite Schritt wird über ein Subselect
3.14 Fetching und Caching
78
realisiert. Daher auch der Name dieser Strategie. Beim Einladen von Entitäten
mit mehreren Collections (von Entitäten) ist diese Strategie dem Join-Fetching
vorzuziehen.
Der Nachteil dieser Variante besteht darin, das ein SELECT-Statement mehr als
beim Join-Fetching benötigt wird.
Die gewählte Strategie kann im Mapping definiert werden. Es ist aber auch möglich,
diese Strategie bei einer Abfrage (HQL, Criteria) explizit anzugeben und somit die
Konfiguration des Mappings zu überschreiben.
from Gegenstand g join fetch g.muttersprache
Sessions offenhalten
Beim Lazy Loading von Hibernate gibt es eine Einschränkung. Referenzierte Entitäten
können nur solange nachgeladen werden, solange die Session offengehalten wird.
Wird die Session geschlossen und auf ein Element eines Proxies zugegriffen, welcher
noch nicht nachgeladen wurde, wird eine LazyInitializationException geworfen.
Abhilfe schafft hier die statische Methode Hibernate.initialize(Object proxy).
Diese Methode läd die Entität proxy nach, auch wenn die Session bereits geschlossen wurde. Mithilfe der Methode
Hibernate.isInitialized(Object proxy) kann überprüft werden, ob das übergebene Objekt bereits eingeladen wurde.
Problem der Langen Sessions
Wie oben beschrieben, muss die Session beim Lazy Loading offengehalten werden,
wenn man sich die Mühe des händischen“ Nachladens mittels der Methode
”
initialize() ersparen will.
Dabei kann es zu langen Sessions kommen.
Benötigt der Benutzer viel Zeit zum Bearbeiten eines Gegenstandes und ändert er
anschließend noch die Muttersprache, welche bis dorthin lediglich als Proxy gehalten wurde, so muss auch die Session dementsprechend lange offengehalten werden.
Greifen viele Benutzer in dieser Zeit auf die Datenbank zu und halten dabei ebenfalls
weitere Sessions offen, kann es vorkommen, dass sehr viele Sessions geöffnet sind.
Dies drückt stark auf die Performance der Datenbank, da viele Sessions auch einen
erhöhten Verwaltungsaufwand erfordern.
3.14 Fetching und Caching
79
Im Kapitel 4 werden Lösungen zu diesem Problem gezeigt.
3.14.3
Eager Loading
Das Gegenteil von lazy loading ist das sogenannte eager loading. Dabei werden alle
referenzierten Objekte sofort mitgeladen. Diese Strategie ist nur in Sonderfällen sinnvoll. Würde ein Objekt beispielsweise drei andere Objekte referenzieren, würden diese
sofort mitgeladen. Das ist noch kein Problem. Referenzieren diese drei Objekte jedoch wiederum jeweils drei Objekte und sind diese ebenfalls so konfiguriert, dass sie
eager nachladen, müssen schon insgesamt 13 Objekte auf einmal geladen werden,
unabhängig davon, ob darauf zugegriffen wird oder nicht.
3.14.4
Caching
Jedes Session-Objekt hat einen Cache. Dieser wird als Session-Level-Cache bezeichnet und wurde im Kapitel 3.10 beschrieben.
Second-Level-Cache
Der Second-Level-Cache ist ein weiterer Cache. Dieser arbeitet Session-übergreifend.
Genauer arbeitet er auf SessionFactory-Ebene.
Im Kapitel 3.10 wurde gezeigt, wie eine Session aus einer SessionFactory erzeugt
wird. Sinnvoll ist es, ein SessionFactory-Objekt applikationsweit zu teilen. Dadurch
existiert der Cache genau einmal für das gesamte System, was der Java Virtual Machine (JVM) entspricht.
Wird ein Datenbankzugriff ausgeführt, wird bei aktiviertem Second-Level-Cache zunächst überprüft, ob die Daten bereits im Cache bereitstehen. Werden die entsprechenden Daten gefunden, wird kein Datenbankzugriff mehr nötig.
Dadurch ergibt sich ein Performance-Vorteil, da der Zugriff auf den Cache wesentlich
schneller als jener auf die Datenbank ist.
Jedoch entsteht durch den Second-Level-Cache auch ein Verwaltungs-Overhead.
Zum Einen muss dieser Cache von Hibernate verwaltet werden.
Zum Anderen wird bei jeder Datenbankabfrage zunächst der Cache durchsucht. Wird
die Entität dort nicht gefunden, so war dieses Durchsuchen überflüssig und stellt einen
Performancenachteil dar.
3.14 Fetching und Caching
80
Ein systemweiter Cache sollte deshalb dann verwendet werden, wenn häufig auf dieselben Entitäten zugegriffen wird.
Es gibt folgende Caching-Strategien:
• Read-Only
Bei dieser Strategie wird ausschließlich aus dem Cache gelesen.
• Read-Write
Hierbei können die Daten nicht nur gelesen, sondern auch verändert werden.
Diese Strategie ist nicht mit dem Transaktionsisolationslevel Serializable verwendbar.
• Nonstrict Read-Write
Auch hier können Daten verändert und gelesen werden. Bei dieser Strategie wird
keine Transaktionsisolation vorgenommen. Sie ist daher nur dann sinnvoll, wenn
selten konkurrierende Datenzugriffe erfolgen. Vorteil dieser Variante ist die bessere Performance im Vergleich zur Strategie Read-Write.
• Transactional
Diese Strategie wird in einer JTA-Umgebung verwendet.
Es gibt verschiedene Implementierungen des Hibernate Second-Level-Caches. Nicht
jeder Cache unterstützt jede Caching-Strategie. Die Wichtigsten sind: EHCache, OSCache, SwarmCache, JBoss Cache. Diese vier Implementierungen sind open sourceProdukte.
3.15 Entwicklungsstrategien
3.15
81
Entwicklungsstrategien
Im Wesentlichen gibt es vier Strategien: Top down, Bottom up, Middle-Out und Meetin-the-Middle.
3.15.1
Top down
Bei dieser Strategie wird zunächst ein Java Domainenmodell erstellt.
Im nächsten Schritt werden die Mapping-Informationen hinzugefügt. Daraus kann später
ein Datenbankschema erzeugt werden. Dies erfolgt mittels geeigneter Hibernate-Tools
wie z.B. hbm2ddl.
3.15.2
Bottom up
Hier ist das Datenbankschema bereits vorhanden.
Mittels geeigneter Reverse-Engineering-Tools ist es möglich, das komplette Domainenmodell inklusive aller Mapping-Informationen generieren zu lassen.
Die meisten Datenbanken leben länger als die Software, die darauf aufsetzt. Somit ist
dies gängige Praxis in der Softwareentwicklung.
Nachteil dieser Strategie ist, dass Konzepte relationaler Datenbanken häufig nur
händisch“ in die objektorientierte Welt übertragbar sind. Ein Beispiel hierfür ist die
”
Vererbung.
3.15.3
Middle-Out
Hierbei werden zunächst die Mapping-Informationan (XML-Mapping) erstellt. Daraus
wird sowohl das Datenbankschema, als auch das Objektmodell erzeugt.
Diese Strategie ist vor allem bei kleinen, übersichtlichen Projekten vorteilhaft.
3.15.4
Meet-in-the-Middle
Hier existieren Objektmodell und Datenbankschema bereits. Die Abstimmung dieser
beiden Komponenten erfolgt über die Mapping-Informationen.
3.15 Entwicklungsstrategien
82
Diese Strategie ist nur in Ausnahmefällen sinnvoll. Im Speziellen dann, wenn andere
Systeme auf bestehendem Objektmodell und Datenbank aufsetzen.
Kapitel 4
Pattern
4.1 Übersicht
In diesem Kapitel werden Pattern vorgestellt, welche unterschiedliche Verwendungsmöglichkeiten von Sessions und Transaktionen beschreiben. In diesem Zusammenhang sind
Detached Entities von entscheidender Bedeutung.
Detached Entities
Im Kapitel 3.10 wurde der Lebenszyklus einer Hibernate-Enität gezeigt. Wird eine Entität von der Session losgelöst (d.h. die Session wird geschlossen), so befindet sich diese im Zustand detached. Änderungen an der Entität wirken sich nicht auf den SessionLevel-Cache aus.
Um Detached Entities wieder in den Zustand persistent zu überführen, muss die Entität wieder an eine Session gebunden werden.
Detached Entities haben den Nachteil, dass das Lazy Loading von Hibernate hier nicht
mehr ohne Zusatzaufwand anwendbar ist.
Wie im Kapitel 3.14 beschrieben muss die Entität beim Lazy Loading an eine Session
gebunden sein. D.h. die Entität muss im Zustand persistent sein.
Beim Arbeiten mit Detached Entities muss daher entweder auf Lazy Loading verzichtet werden, oder die Methode Hibernate.initialize(Objekt proxy) verwendet werden.
4.2 Session per Request
84
Bei den nachfolgend vorgestellten Pattern wird nicht auf die Überprüfung der konkurrierenden Benutzerzugriffe eingegangen. Diese wurden bereits im Kapitel 3.12 beschrieben.
4.2
Session per Request
Hierbei wird pro Datenbankanfrage eine Session geöffnet. Die Lebensdauer einer Session ist sehr kurz.
Bei einer Benutzeranfrage wird eine Session geöffnet, alle Operationen der Anfrage
durchgeführt und anschließend wird die Session sofort wieder geschlossen. Werden
Datenbankänderungen durchgeführt, so entspricht die Lebenszeit einer Transaktion
jener der Session.
Wird eine Entität mit dieser Strategie geladen, handelt es sich nach dem Schließen der
Session um eine Detached Entity. Soll die geladene Entität wieder in die Datenbank
gespeichert werden, so muss diese an eine Session gebunden werden. Zu diesem
Zweck wird eine neue Session geöffnet, alle Operationen durchgeführt und anschließend wird die Session wieder geschlossen.
Bei der Bearbeitung eines Gegenstands würde dies folgendermaßen realisiert:
1. Benutzeranfrage
Der/Die Benutzer/in wählt den Menüpunkt Gegenstand bearbeiten“.
”
2. Session öffnen
Der ausgewählte Datensatz wird eingeladen.
3. Session schließen
Unmittelbar nach dem Einladen wird die Session wieder geschlossen. Die Entität
befindet sich somit im Zustand detached.
1
2
3
4
5
//..
Session session = sessionFactory.openSession();
aktGegenstand=(Gegenstand)session.load(Gegenstand.class,22);
session.close();
//..
Listing 4.1: Einladen einer Entitaet bei Session per Request
4.3 Session per Conversation
85
4. Bearbeitungszeit
Der/Die Benutzer/in bekommt ein Formular zum Bearbeiten des Gegenstandes
am Bildschirm angezeigt. Er/Sie hat dafür viel Zeit. Schließlich ist keine Session
geöffnet.
5. Benutzeranfrage
Der/Die Benutzer/in ist mit dem Bearbeiten des Gegenstandes fertig, und will
diese Änderungen speichern.
6. Session öffnen
Eine Transaktion wird geöffnet und die Änderungen der Entität persistiert.
7. Session schließen
Die Session wird sofort wieder geschlossen. Die Interaktion mit dem/der Benutzer/in ist abgeschlossen.
1
2
3
4
5
6
7
//..
Session session = sessionFactory().openSession();
Transaction tx = session.beginTransaction();
session.update(aktGegenstand);
tx.commit();
session.close();
//..
Listing 4.2: Speichern einer Entitaet bei Session per Request
Diese Strategie hat den großen Vorteil, dass die Sessions nur sehr kurz offengehalten
werden. Benötigt der/die Benutzer/in lange um eine Entität zu bearbeiten, ist das kein
Problem. Schließlich können Detached Entities unbegrenzt lange gehalten werden.
Des Weiteren ist diese Strategie einfach realisierbar und für Mehrbenutzerapplikationen gut geeignet.
Nachteil sind die Probleme von Detached Entities im Zusammenhang mit Lazy Loading.
4.3
Session per Conversation
Diese Strategie ist bei längeren Benutzerinteraktionen zu empfehlen. Ein Beispiel hierfür
ist eine Konversation mit Frage/Antwort-Zyklen zwischen Benutzern und System.
Wie der Name schon sagt, wird hier die Session erst wieder geschlossen, wenn die
Benutzerinteratkion beendet wird. Werden Entitäten geladen, bleiben diese im Zustand
persistent. Somit werden Änderungen an der Entität sofort im Session-Level-Cache
4.3 Session per Conversation
86
wirksam.
Anders als beim Session per Request kann es in einer Session mehrere Transaktionen
geben. Die Lebenszeit von Session und Transaktion ist bei diesem Pattern somit nicht
gleich.
Bei der Bearbeitung eines Gegenstands würde dies folgendermaßen realisiert:
1. Benutzeranfrage
Der/Die Benutzer/in wählt den Menüpunkt Gegenstand bearbeiten“.
”
2. Session öffnen
Der ausgewählte Datensatz wird eingeladen, die Session bleibt geöffnet.
3. Bearbeitungszeit
Der/Die Benutzer/in bekommt ein Formular zum Bearbeiten des Gegenstandes
am Bildschirm angezeigt. Die Session wird dabei die ganze Zeit offengehalten.
4. Benutzeranfrage
Der/Die Benutzer/in ist mit dem Bearbeiten des Gegenstandes fertig, und will
diese Änderungen speichern.
5. Transaktion öffnen
Die Änderungen an der Entität werden persistiert.
6. Session schließen
Die Session wird erst jetzt geschlossen. Die Interaktion mit dem/der Benutzer/in
ist abgeschlossen.
Folgendes Listing zeigt das grundsätzliche Schema der Implementierung bei Session
per Conversation:
1
2
3
4
5
6
7
8
9
10
11
12
13
//1. Benutzerinteraktion
Session session = sessionFactory().openSession();
aktGegenstand = (Gegenstand) session.load(Gegenstand.class, 22);
//..
//2. Benutzerinteraktion
aktGegenstand.setGegKurzbez("Prog");
aktGegenstand.setGegLangbez("Programmieren");
//..
//3.Benutzerinteraktion
Muttersprache msp = aktGegenstand.getMuttersprache();
4.4 Open Session in View
14
15
16
17
18
19
87
//..
//4. Benutzerinteraktion
Transaction tx = session.beginTransaction();
session.update(aktGegenstand);
tx.commit();
session.close();
Listing 4.3: Session per Conversation
In Zeile 12 wird erstmals auf die Muttersprache zugegriffen und diese aus der Datenbank geladen. Vor Abarbeitung dieser Zeile, wurde diese Muttersprache-Entität durch
einen Proxy repräsentiert.
Dieses Pattern hat Vorteile bei längeren Benutzerinteraktionen. Bei dieser Strategie
ist, anders als bei Session per Request, Lazy Loading kein Problem.
4.4
Open Session in View
Dieses Pattern ist zum Einsatz in einer Webapplikation konzipiert und stellt eine Weiterentwicklung des Session per Request-Pattern dar.
Ein typisches Problem einer Webapplikation ist, dass die Ausgabe, z.B. ein Bearbeitungsformular, erst nach der Abarbeitung der Programmlogik gerendert wird. Zu diesem Zeitpunkt wurde die Session aber bereits geschlossen. Der Zustand einer geladenen Entität ist somit detached. Beim Rendern der Ausgabe kann es vorkommen, dass
auf assoziierte Entitäten zugegriffen wird, welche noch nicht geladen wurden. Dadurch
wird eine LazyInitializationException geworfen.
Zur Illustrierung dieses Problems wird das Gegenstand-Beispiel herangezogen.
Will ein/e Benutzer/in einen Gegenstand bearbeiten, so wird der Gegenstand in einer
Session eingeladen. Die Session wird danach geschlossen.
Beim Gegenstand-Mapping wurde das Standardverhalten Lazy Loading beibehalten.
Dadurch wird die assozierte Entität Muttersprache nicht geladen. Stattdessen wird ein
Proxy verwendet. Beim Rendern des Bearbeitungsformulars wird auf den Namen der
Muttersprache zugegriffen. Dieses Objekt ist jedoch nicht geladen. Und da die Entität
Gegenstand im Zustand detached ist, wird beim Zugriff darauf eine
LazyInitializationException geworfen.
4.4 Open Session in View
88
Beim Open Session in View-Pattern wird die Session solange offengehalten, bis alle
benötigten Daten zum Rendern der Ausgabe geladen wurden. Dafür müssen in der
Webapplikation entsprechende Listener, Servlet-Filter u.Ä. implementiert werden.
1. Benutzeranfrage
Der/Die Benutzer/in wählt den Menüpunkt Gegenstand bearbeiten“.
”
2. Session öffnen
Der ausgewählte Datensatz wird eingeladen.
3. Die Ausgabe wird gerendert
Dabei werden beim Zugriff des Systems auf die Proxies alle benötigten Entitäten
eingeladen.
4. Session schließen
Unmittelbar nach dem Rendern wird die Session wieder geschlossen. Die Entität
befindet sich somit im Zustand detached.
Das Schließen der Session kann zum Beispiel in einem PhaseListener erfolgen.
5. Bearbeitungszeit
Der/Die Benutzer/in bekommt ein Formular zum Bearbeiten des Gegenstandes
am Bildschirm angezeigt. Er/Sie hat dafür viel Zeit. Schließlich ist keine Session
geöffnet.
6. Benutzeranfrage
Der Benutzer ist mit dem Bearbeiten des Gegenstandes fertig, und will diese
Änderungen speichern.
7. Session öffnen
Eine Transaktion wird geöffnet und die Änderungen der Entität persistiert.
8. Session schließen
Die Session wird sofort wieder geschlossen. Die Interaktion mit dem Benutzer ist
abgeschlossen.
Bei der Strategie Open Session in View können die Stärken des Lazy Loading voll
ausgeschöpft werden. Es werden für ein Web-Formular genau jene assoziierten Entitäten mitgeladen, die zur Erzeugung der Ausgabe benötigt werden.
Wie oben erwähnt, ist dieses Pattern eine Weiterentwicklung des Session per Request-Pattern für Webapplikationen. Das Konzept, Sessions kurzlebig zu halten wird
beibehalten.
Kapitel 5
Umsetzung im Projekt SAS III
5.1 Übersicht
Im Projekt SAS III stand die Datenbank beim Projektbeginn bereits zur Verfügung. Es
handelt sich dabei um eine PostgreSQL-Datenbank.
PostgreSQL ist eine Open-Source Datenbank, welche großteils konform dem ANSISQL 92 Standard ist. Sie wird unter der BSD1 -Lizenz verbreitet.
Als Entwicklungsstrategie wurde Bottom up gewählt.
Aufgabe dieser Projektgruppe war die Erstellung der SAS-Webapplikation, welche es
dem/der Benutzer/in ermöglicht, Daten einzugeben bzw. zu manipulieren. Dies wurde
in From von Webformularen realisiert. Diese Formulare sind dabei sehr häufig datenintensiv. Schließlich gibt es in der Datenbank, auf die dieses Projekt aufsetzt, Tabellen
mit einer Spaltenanzahl von mehr als 80.
Dadurch war die Performance bei der Implementierung eine große Herausforderung.
1
Berkeley Software Distribution
5.2 Aufbau der Applikation
5.2
90
Aufbau der Applikation
5.2.1 Übersicht
Die SAS III-Webapplikation verwendet neben Hibernate die Technologien Facelets und
ICEfaces.
Der OR-Mapper Hibernate ist für die Datenbankanbindung zuständig. Facelets und
ICEfaces werden für die Oberflächengestaltung der Webapplikation eingesetzt.
Der interne Aufbau der Applikation entspricht der Komponentenstruktur von EJB.
5.2.2
POJO und Mapping-Informationen
Die Mapping-Informationen werden in der Form des XML-Mappings implementiert.
Die Entscheidung zu dieser Variante war großteils dadurch bedingt, dass die Implementierung dieses Softwareprojektes mit dieser Technik begann und erst im Laufe
dieser Diplomarbeit wurde das Annotation-Mapping analysiert.
Ein Vorteil dieser Variante ist jedoch, das Konzepte des OR-Mappings dadurch besser
ersichtlich sind und die Einarbeitung in Hibernate einfacher ist.
Des Weiteren ist es für Hibernate-unerfahrene Anwendungsentwickler einfacher, Persistenzklassen zu verwenden, welche nicht durch Annotations an Übersichtlichkeit verlieren.
Es gibt somit zu jeder Entität zwei Klassen. Die Entitäts-Klasse (POJO) und die zugehörige XML-Mapping-Datei. Beide Dateien liegen im Paket model.
Eine Mapping-Datei enthält Mapping-Information zu genau einem zugeordneten POJO. Mehrfachmappings in einer XML-Datei werden nicht verwendet.
Die Namen der beiden zugehörigen Dateien sind äuqivalent (z.B. Gegenstand.java
- Gegenstand.hbm.xml, Muttersprache.java - Muttersprache.hbm.xml, ...).
Die POJOs verfügen jeweils über einen parameterlosen Konstruktor, einen Konstruktor
für alle Spalten die einen not null-Constraint besitzen (diese Instanzvariablen dürfen
beim Speichern nicht null sein) und einen Konstruktor mit allen Instanzvariablen.
Des Weiteren gibt es zu jeder Instanzvariable entsprechende Get- und Set-Methoden.
Hibernate greift standardmäßig auf Instanzvariablen über diese Methoden zu. Dieses
Verhalten wurde beibehalten.
5.2 Aufbau der Applikation
91
Die POJOs sind serialisierbar.
Sie implementieren das Interface java.io.Serializable.
Die Methode getLastChange() wurde in nahezu jeder Entitäts-Klasse implementiert. Sie liefert die letzte Datenbankänderung der Entität. Ansonsten stellt ein POJO
keine weitere Programmlogik bereit.
Ein POJO im Projekt SAS III hat somit grundsätzlich folgenden Aufbau:
1 public class Entity implements java.io.Serializable{
2
//Instanzvariablen
3
4
//Parameterloser Konstruktor
5
6
//Not-Null-Konstruktor
7
8
//Konstrukor für alle Instanzen
9
10
//Getter & Setter
11
12
//Methode getLastChange()
13 }
Listing 5.1: Aufbau eines POJOs in SAS III
Bei den Mapping-Dateien wurde soviel Information als möglich integriert. D.h. auch
optionale Parameter wurden weitestgehend gefüllt.
Werden im Mapping nur zwingend vorgeschriebene Parameter mit Werten versorgt,
holt sich Hibernate diese Informationen zur Laufzeit. Es wäre beispielsweise möglich,
den Datentyp (Mapping-Element type) einer Property nicht anzugeben. Hibernate
könnte sich den Typ zur Laufzeit über die Dekleration im POJO holen. Dies wirkt sich
jedoch negativ auf die Performance aus.
5.2.3
Konfiguration
Hibernate wird in der Applikation SAS III mittels der hibernate.cfg.xml-Datei konfiguriert. Diese befindet sich im default-package. Sie wird somit beim Erzeugen eines
Configuration-Objekts mittels der Methode configure() ohne implizite Angabe
des Pfades eingelesen.
5.2 Aufbau der Applikation
92
Da die Applikation eine Postgres-Datenbank verwendet, wird der PostgreSQL JDBC Treiber verwendet. Der Hibernate Dialekt dafür ist in
org.hibernate.dialect.PostgreSQLDialect definiert.
1
2
3
4
5
6
<property name="hibernate.dialect">
org.hibernate.dialect.PostgreSQLDialect
</property>
<property name="hibernate.connection.driver_class">
org.postgresql.Driver
</property>
Listing 5.2: Konfiguration des Datenbankdialekts
5.2.4
SessionFactory
Im Projekt SAS III hat jeder/jede Benutzer/in eine eigene SessionFactory.
Beim Anmelden am System wird für den/die Benutzer/in eine neue SessionFactory instanzieert.
Somit besitzt jeder/jede Benutzer/in auch einen eigenen Second-Level-Cache. Die
Implementierung des Second-Level-Caches ist Aufgabe einer anderen Diplomarbeit.
Deshalb wird dieses Thema hier nicht näher behandelt.
Die Klasse HibernateUtil, welche nach dem Entwurfsmuster Singleton implementiert wurde, hat folgenden Aufbau:
1 public class HibernateUtil {
2
3
private SessionFactory sessionFactory;
4
private Configuration configuration;
5
6
public HibernateUtil() {
7
}
8
9
public synchronized SessionFactory getSessionFactory(String ←dbUser, String dbPassword) {
10
if (sessionFactory == null) {
11
configuration = new Configuration()
12
.configure()
13
.setProperty("hibernate.connection.username", ←dbUser)
5.2 Aufbau der Applikation
14
15
16
17
18
19
20
21
22
23 }
.setProperty("hibernate.connection.password",
dbPassword);
sessionFactory = configuration.buildSessionFactory();
93
←-
}
return sessionFactory;
}
public Configuration getConfiguration() {
return configuration;
}
Listing 5.3: SessionFactory im Projekt SAS III
Die beiden Attribute sessionFactory und configuration können sich über die
entsprechenden Getter gegeben werden lassen.
Die Methode getSessionFactory() erzeugt das Configuration- und
SessionFactory()-Objekt beim Anmelden eines Benutzers. Dies geschieht pro Benutzer/in nur einmal, anschließend wird bei jedem Aufruf von getSessionFactory()
die bereits instanzieerte SessionFactory zurückgeliefert.
Es ist zu beachten, dass in Zeile 12 über die Methode configure() die Konfigurationseinstellungen der hibernate.cfg.xml eingelesen wird. In den Zeilen 13 und 14
wird die Konfiguration im Bezug auf den/die Benutzer/in durch die Methoden
setProperty() überschrieben. Somit geht jeder/jede Benutzer/in mit seinen/ihren
individuellen Rechten auf die Datenbank und besitzt eine eigene SessionFactory.
5.3 Views
5.3
94
Views
5.3.1 Übersicht
Im Projekt SAS III werden in vielen Fällen Views verwendet.
Ziel dahinter ist zum Einen die Verbesserung der Performance. Zum Anderen werden
bestimmte Systeme dadurch vereinfacht.
Zur Illustration wird in folgendem Beispiel die Personal-View gezeigt:
In der Datenbank existiert die Tabelle personal. Diese Tabelle besitzt einen ForeignKey auf die Tabelle titel.
In der Tabelle titel stehen alle möglichen Titel, die für eine Person in SAS III relevant sein können.
Wird auf einen Datensatz zugegriffen und soll dabei auch der Titelname geladen werden, wird ein Join oder ein zusätzliches Select-Statement nötig.
Abbildung 5.1 verdeutlicht diesen Sachverhalt.
Abbildung 5.1: Tabellenstruktur ohne Views
In vielen Formularen wird eine Dropdownbox des gesamten Personals angezeigt.
Beim Formular zur Bearbeitung einer Abteilung ist dies, wie in Abbildung 5.2 ersichtlich, bei der Wahl des Abteilungsvorstandes der Fall.
5.3 Views
95
Abbildung 5.2: Dropdownfeld auf einem Webformular
Um die Titel vor dem Namen der potentiellen Abteilungsvorstände stehen zu haben,
wäre für jede Personal-Entität ein zusätzlicher Select nötig.
Mithilfe einer View wird dieses Problem gelöst und die Performance gesteigert.
Abbildung 5.3: Views anstelle von Tabellen
In der View gibt es nun, wie in Abbildung 5.3 ersichtlich, die neue Spalte per_name,
welche Titel und Name zusammenfasst.
Dieses Konzept kommt in SAS III in vielen Fällen zum Einsatz.
5.3 Views
5.3.2
96
Navigationsbäume
Eine weitere Anwendung von Views im Projekt SAS III sind die in Abbildung 5.4 ersichtlichen Navigationsbäume.
Abbildung 5.4: Navigationsbaum der SAS III-Webapplikation
Dieser Navigationsbaum wird für die Benutzer individuell dargestellt.
Ein Lehrer bekommt beispielsweise nur jene Klassen bzw. Schüler angezeigt, die er
unterrichtet.
Hierzu wurden Views angelegt, welche genau diese Informationen individuell für die
angemeldeten Benutzer liefert.
Des Weiteren bestehen diese Views nur aus Spalten, welche zur Anzeige des Baums
benötigt werden.
5.3 Views
97
Jeder Knotenpunkt ist ein Java-Objekt, welches eine Collection der Subknoten hat.
Wie in Abbildung 5.4 ersichtlich, ist dies beispielsweise bei der Klasse der Fall. Diese
enthält eine Collection aller Schueler-Subknoten.
Zu diesem Zweck wird folgende Klasse verwendet:
1 public class Klasse_Baum {
2
3
private Integer id;
4
private String bezeichnung;
5
private Set<Schueler_Baum> schueler;
6
7
//Getter und Setter...
8 }
Listing 5.4: Baumknotenklasse
Diese Klasse enthält nur notwendige Daten, um den Baum performant zu halten.
Das Mapping wird folgendermaßen realisiert:
1 <hibernate-mapping>
2
<class name="model.tree.Klasse_Baum" table="vi_tree_klasse">
3
<id column="kla_id" name="id">
4
<generator class="assigned"/>
5
</id>
6
<property column="kla_bezeichnung" name="bezeichnung" ←type="string"/>
7
8
<set name="schueler" order-by="ssd_schueler" ←table="vi_tree_schueler">
9
<key column="ssd_kla_id"/>
10
<one-to-many class="model.tree.Schueler_Baum"/>
11
</set>
12
</class>
13 </hibernate-mapping>
Listing 5.5: Klassen-Knotenpunkt im Navigationsbaum
Die Wahl der Generatorstrategie in Zeile 4 ist unwichtig. Schließlich wird diese Entität
niemals in die Datenbank geschrieben.
5.3 Views
98
In den Zeilen 7,8,9 und 10 wird eine Collection erzeugt. Diese wird mittels
order-by="ssd_schueler" sortiert.
Es handelt sich, wie in Zeile 9 ersichtlich um den one-to-many Beziehungstyp. Eine
Klasse hat mehrere Schüler.
5.3.3
Mapping Generator
Bei der Entwicklungsstrategie Bottum up können Mapping-Dateien und POJOs mittels
geeigneter Reverse-Engineering-Tools erzeugt werden.
Auch die Entwicklungsumgebung Netbeans 6.7.1, in der SAS III entwickelt wird, stellt
ein solches Tool zur Verfügung.
Diese Tools sind jedoch nicht für das mappen von Views geeignet. Der Grund dafür
liegt in der Tatsache, dass bei Views keine Informationen über Constraints hinterlegt
sind. Es können daher nur Name und Datentyp der Spalten ausgelesen werden.
Im Projekt SAS III werden, wie oben beschrieben, häufig Views verwendet.
Es wurde daher nötig, ein Tool zu entwickeln, welches das mappen von Views realisiert.
Zu diesem Zweck wurde im Rahmen dieser Diplomarbeit ein Reverse-EngineeringTool entwickelt. Es handelt sich dabei um eine Java-Applikation.
5.3 Views
99
Beim Start der Applikation muss, wie in Abbildung 5.5 ersichtlich, zunächst die Datenbankverbindung konfiguriert werden.
Abbildung 5.5: Datenbankverbindung beim Mappinggenerator herstellen
Der Inhalt des Passwortfeldes wird nicht angezeigt. Im Feld Server wird die Adresse
und Port des Servers angegeben. Die Checkbox Anmeldedaten speichern sorgt dafür,
dass beim nächsten Start des Tools alle Felder vorausgefüllt sind. Einzig das Passwortfeld wird aus Sicherheitsgründen nicht gespeichert.
Die Dropdown-Box DB Typ dient der Konfiguration des verwendeten Datenbanktreibers. Dieser Generator ist für die Verwendung in verschiedenen Umgebungen konzipiert. So ist es zurzeit möglich, die Open-Source-Datenbanken Postgres und MySQL
zu verwenden.
Abbildung 5.6: Auswahl der Datenbank
Werden weitere Datenbanken verwendet, ist es kein Problem, diese zu implementie-
5.3 Views
100
ren.
Datenbankunabhängiger Quelltext wird dadurch erreicht, das die Datentypen nicht
nach Name überprüft werden. Der Name von Datentypen unterscheidet sich schließlich von Datenbank zu Datenbank.
Stattdessen wird nach den in java.sql.Types definierten Typen verglichen.
Nach Eingabe aller Verbindungsdaten, wird die Verbindung nach einem Klick auf den
Button Connect hergestellt. Sind die eingegebenen Einstellungen nicht korrekt, so
wird eine Fehlermeldung angezeigt und die Verbindungsdaten können neu eingegeben werden. Ist die Verbindung erfolgreich, so wird das Haupfenster des Generators
geöffnet.
Wie oben erwähnt, ist dieser Generator speziell für Views erzeugt worden. Zu diesem
Zweck wird eine Tabelle mit allen Spalten der ausgewählten View angezeigt. Der/Die
Benutzer/in kann dann Constraints hinzufügen.
Das Hauptfenster des Mappinggenerators ist in Abbildung 5.7 zu sehen.
5.3 Views
101
Abbildung 5.7: Hauptfenster des Mappinggenerators
5.3 Views
102
In der Combobox, welche sich am oberen Ende des Hauptfenster des Mappinggenerators befindet, kann jede Tabelle und View, die es in der Datenbank gibt ausgewählt
werden. In diesem Fall wurde die View vi_t_personal verwendet. Alle Vorgaben
des Generators wurden belassen.
Eine Spalte einer Tabelle/View wird in einer Zeile angezeigt.
Der Button POJO und Mapping-File erzeugen erstellt die im Namen des Buttons enthaltenen Dateien in der Form des Mapping mit XML.
Der Button Konfigurationsdatei erzeugen erstellt eine hibernate.cfg.xml mit den
Einstellungen, die zuvor beim Herstellen der Datenbankverbindung eingegeben wurden.
Folgend werden die Spalten der Tabelle des Mapping Generators, welche in Abbildung
5.7 zu sehen sind, beschrieben:
• PK
Diese Spalte definiert den Primärschlüssel. In Abbildung 5.7 ist kein Primärschlüssel vergeben, da die Constraints bei einer View wie oben erwähnt nicht
ausgelesen werden können. Würde nun versucht, die Ausgabedateien zu erstellen, wird eine Fehlermeldung ausgegeben.
Abbildung 5.8: Fehlermeldung wenn kein Primaerschlüssel ausgewaehlt wurde
Jede Hibernate-Entität muss einen Primärschlüssel besitzen. Wird mehr als eine Spalte ausgewählt, werden diese Spalten in einer ID-Klasse zusammengefasst und mittels der composite-id, wie im Kapitel Identität beschrieben, eingebunden. Der Generator implementiert auch gleich die Methoden equals() und
hashCode() in der ID-Klasse. Es werden daher drei Ausgabedateien erzeugt.
Der Generator ist somit in der Lage, fachliche Primärschlüsseltabellen und Views
zu mappen.
• DB Spaltenname
In dieser Spalte wird der Datenbankspaltenname angezeigt. Diese Spalte kann
sinnvollerweise von Benutzern nicht verändert werden.
5.3 Views
103
• Instanzvariablenname
In der Spalte Instanzvariablenname werden die in Java-Bean-Standard konvertierten Namen der Instanzvariablen angezeigt. Die Benutzer haben hierbei die
Freiheit, diese vorgeschlagenen Bezeichnungen zu verändern. In Abbildung 5.7
wurden die Namen so belassen wie sie vom Generator vorgeschlagen wurden.
• Java-Typ
Diese Spalte zeigt den jeweils verwendeten Java-Datentyp der Instanzvariablen
an.
• Length
In dieser Spalte wird die maximale Zeichenanzahl der Datenbankspalten angezeigt. Dieses Feld kann von Benutzern verändert werden. Durch dieses Feld werden Datenintegritätsregeln in das Mapping einbezogen.
• Not Null
Auch diese Spalte hat den Zweck, Datenintegritätsregeln in das Mapping einzubeziehen. Diese Spalte ist in engem Kontakt mit der Spalte Java-Typ zu sehen. Ist
das Not Null-Feld für eine bestimmte Zeile ausgewählt, so wird hier ein primitiver
Datentyp für die Instanzvariable verwendet (z.B. int).
Schließlich muss diese Spalte bei einem Datenbank-INSERT einen Wert besitzen und darf nicht null sein. Ist das Feld Not Null nicht ausgewählt, so darf
in diese Spalte auch null eingefügt werden. Deshalb wird hier ein Wrappertyp
verwendet (z.B. java.lang.Integer).
• Kommentar
In diesem Feld kann eine Anmerkung zur Datenbankspalte bzw. Instanzvariable
gegeben werden. Diese wird in das Mapping mittels des <comment>-Elements
und ins POJO als Kommentar neben der Instanzvariablendeklaration übernommen.
• Objekt laden
Mithilfe dieser Spalte kann eine Assoziation zu einer Entität erzeugt werden. Dabei wird eine normale Datenbankreferenz auf das Mapping abgebildet. Dies wird,
wie im Kapitel 3.7 beschrieben, durch das <many-to-one>-Element implementiert. Der Mappinggenerator erstellt jeweils unidirektionale Beziehungen.
5.3 Views
104
Wird dieses Feld ausgewählt, erscheint ein Dialog, mit welchem die Beziehung
konfiguriert werden kann.
Abbildung 5.9: Anlegen einer Beziehung
Hier kann optional eine Tabelle ausgewählt werden. Diese Auswahl ist jedoch
optional. Entscheidend ist der Klassenname.
Ist man mit der Konfiguration fertig, so kann der Button POJO & Mapping - File erzeugen gedrückt werden. Dadurch werden die Ausgabedateien erstellt. Der Ort und Name
der Dateien kann, wie in Abbildung 5.10 zu sehen, in einem JFileChooser-Dialog
bestimmt werden.
Standardmäßig wird hier der Desktop als Speicherziel vorausgewählt.
5.3 Views
105
Abbildung 5.10: Speicherort festlegen
Der Mappinggenerator geht noch einen Schritt weiter und erlaubt neben dem Mappen
von Views auch das Reverse-Engineering an normalen Tabellen. Constraints werden
dabei aus der Tabelle ausgelesen und im Hauptfenster vorausgewählt. In Abbildung
5.11 ist ein vorausgefülltes Hauptfenster zu sehen.
5.3 Views
Abbildung 5.11: Vorausgefuelltes Hauptfenster beim Mappen von Tabellen
106
5.4 Sessions, Transaktionen und Lazy Loading
5.4
107
Sessions, Transaktionen und Lazy Loading
Wie schon in der Übersicht erwähnt, war die Performance ein wesentlicher Bestandteil
bei der Implementierung der SAS-Webapplikation.
Aufgabe war es, die Dateneingabe bzw. Manipulation zu implementieren. Die Generierung von Reports, wie z.B. Zeugnisse oder Schülerlisten, war Aufgabe einer anderen
Projektgruppe.
In Abbildung 5.12 ist ein Bearbeitungsformular zu sehen.
Abbildung 5.12: Webformular von SAS III
5.4 Sessions, Transaktionen und Lazy Loading
108
Hier wird ersichtlich, dass es keinen Sinn machen würde, Objekreferenzen im Mapping zu verwenden. Bei den Feldern Anrede, Titel und Status wird eine DropdownBox verwendet, welche jeweils alle möglichen Eingaben, die Benutzer tätigen können,
beinhalten. Diese werden mittels Criteria wie in Kapitel 5.7 gezeigt, aus den jeweiligen
Tabellen geladen.
Im Mapping werden lediglich die einzelnen Spalten als Werttypen geladen. Auch bei
Referenzen auf Datenbankseite wird der Fremdschlüssel ebenfalls als Werttyp geladen. Wird das assoziierte Objekt benötigt, kann es über den Fremdschlüssel nachgeladen werden. Die Tabellen und Views werden daher flach geladen.
Lazy Loading wird nicht verwendet. Werttypen werden von Hibernate sofort geladen
und bei den wenigen im Mapping enthaltenen mitreferenzierten Entitäten wird Lazy
Loading mittels des Parameters lazy="false" deaktiviert. Somit ergeben sich auch
nicht die Probleme des Lazy Loadings, wie beispielsweise das 1+n-Problem oder der
Zugriff auf Proxies außerhalb einer Hibernate-Session.
Bei Datenbankzugriffen wird das Session per Request-Pattern angewandt.
Hibernate-Sessions werden dadurch so kurz als möglich gehalten. In der Bearbeitungszeit des Benutzers auf einem Webformular sind die Entitäten im Zustand detached. Beim Speichern einer Entität wird eine neue Session geöffnet.
Diese Strategie bietet für den benötigten Aufgabenbereich der Webapplikation die beste Performance.
Die nachstehend gezeigte Implementierung wird, stellvertretend für alle anderen Entitäten, anhand der Entität Gegenstand gezeigt.
Das Einladen der Entität Gegenstand wird in der Methode loadAktGegenstand
vorgenommen.
1 public void loadAktGegenstand(int id) {
2
Session session = getUb().getSessionFactory().openSession();
3
try {
4
aktGegenstand=(Gegenstand)session.load(Gegenstand.class,id);
5
} finally {
6
session.close();
7
}
8 }
Listing 5.6: Typisches Laden einer Entitaet in SAS III
Wie im Listing ersichtlich, wird die Session in Zeile 2 geöffnet und nach dem Einladen
des Gegenstandes wieder geschlossen. Die Datenbankoperationen werden im tryBlock durchgeführt. Dadurch wird sichergestellt, dass egal ob ein Fehler auftritt oder
nicht, der Code im finally-Block ausgeführt wird.
5.4 Sessions, Transaktionen und Lazy Loading
109
Lazy Loading wurde im Mapping von Gegenstand deaktiviert. Probleme des Lazy
Loading, wie der Zugriff auf noch nicht initialisierte Proxies, stellen somit kein Problem
dar.
Nach erfolgreichem Einladen wird das Bearbeitungsformular am Browser des Benutzers angezeigt. In der Bearbeitungszeit befindet sich die Entität Gegenstand im Zustand detached.
Ist der/die Benutzer/in mit der Bearbeitung fertig und klickt auf den Speicher-Button,
so kommt folgende Methode zur Ausführung:
1 public void saveAktGegenstand() {
2
Session session = getUb().getSessionFactory().openSession();
3
Transaction tx = session.beginTransaction();
4
try {
5
session.update(aktGegenstand);
6
tx.commit();
7
//Erfolgsmeldung am Bildschirm des Benutzers anzeigen
8
} catch (Exception e) {
9
tx.rollback();
10
//Weitere Fehlerbehandlung und Ausgabe einer Fehlermeldung
11
} finally {
12
session.close();
13
}
14 }
Listing 5.7: Typische Speichermethode in SAS III
In Zeile 2 wird eine neue Session erzeugt. Die Gegenstand-Entität wird in Zeile 4
mittels der Methode update() an diese Session gebunden. In Zeile 5 werden die
Änderungen mittels commit() in die Datenbank geschrieben. Auch hier erfolgen die
Datenbankoperationen im try-Block. Tritt ein Fehler auf, wird die Transaktion mittels
rollback() zurückgerollt. Im finally-Block, welcher sicher zur Ausführung kommt,
wird die Session geschlossen.
5.5 Sperren
5.5
110
Sperren
Bei der Webapplikation SAS III handelt es sich um eine Multi-User-Applikation. Beim
Verändern von Entitäten kann es daher bei konkurrierenden Benutzerzugriffen zum
Überschreiben von Änderungen kommen.
In diesem Projekt werden jedoch weder pessimistische noch optimistische Sperrstrategien verwendet. Das Standardverhalten von Hibernate, Letzter Commit gewinnt (engl.
last commit wins), wird beibehalten.
Die Änderungen des späteren Speichervorganges überschreiben somit den Speichervorgang des früheren.
Die Entscheidung zu dieser Strategie hatte folgende Gründe:
Zum einen wurde auch die Vorgängerapplikation SASII in dieser Form implementiert.
Es hat sich dabei als Best Practice bewährt, diese Strategie zu verwenden.
Zum Anderen werden Benutzern im Projekt SAS III Zugriffsrechte vergeben. Beispielsweise ist der Direktor einer Schule als einziger berechtigt, z.B. die Anschrift der Schule
zu ändern. Ein weiteres Beispiel ist, dass ein Lehrer nur Noten seiner Gegenstände
für Schüler eintragen darf, die er auch wirklich unterrichtet.
Diese Beispiele zeigen, dass Überschneidungen nur selten auftreten.
Der Mehraufwand beim Einsatz von Sperrstrategien wäre daher nicht gerechtfertigt.
5.6
Component-Mapping
In einigen Tabellen der SAS III-Datenbanken, werden Telefonkontakte direkt in die jeweiligen Tabellen integriert. Dies ist beispielsweise bei der Tabelle Abteilung der Fall.
Vier Spalten bilden einen Telefonkontakt.
Dies sind:
• arttel
Art der Telefonnummer. Z.B. Fax, Handy, Telefon.
• staattel
Staatsvorwahl. Z.B. +43 für Österreich.
• vorwahltel
Regionale Vorwahl. Z.B. 02742 für St. Pölten.
5.6 Component-Mapping
111
• rufnrtel
Rufnummer.
In die Tabelle Abteilung können höchstens vier dieser Telefonkontakte eingetragen
werden.
Die Tabelle Abteilung enthält somit 16 Telefonkontaktspalten, wobei jeweils 4 davon
einen Telefonkontakt bilden.
In der Hibernate-Implementierung wurde dies über Component Mapping vereinfacht.
Zu diesem Zweck wurde die Klasse Telefonkontakt implementiert, welche die oben
gezeigten vier Spalten als Instanzen kapselt. Es handelt sich dabei um ein Wertobjekt.
Diese Klasse hat folgenden Aufbau:
public class Telefonkontakt {
private
private
private
private
Character arttel;
Integer staattel;
String vorwahltel;
String rufnrtel;
//...
//Konstruktoren, Getter und Setter
}
Diese Klasse wird in jeder Entität, in der Telefonkontakte vorhanden sind, eingesetzt.
Das Abteilungs-POJO kapselt nun vier Telefonkontakt-Objekte.
public class Abteilung implements java.io.Serializable {
//...
private
private
private
private
//...
Telefonkontakt
Telefonkontakt
Telefonkontakt
Telefonkontakt
telefonkontakt1;
telefonkontakt2;
telefonkontakt3;
telefonkontakt4;
}
In der Mapping-Datei zur Klasse Abteilung ergibt sich dadurch folgender Aufbau:
5.7 Criteria
112
1 <component name="telefonkontakt1" class="model.Telefonkontakt">
2
<property name="arttel" type="java.lang.Character">
3
<column length="1" name="abt_arttel1">
4
<comment>Art 1. Telefon (Foreign-Key telefonart)</comment>
5
</column>
6
</property>
7
<property name="staattel" type="java.lang.Integer">
8
<column name="abt_staattel1">
9
<comment>ID des Staates mit der Landesvorwahl 1. Telefon ( ←Foreign-Key staat)</comment>
10
</column>
11
</property>
12
<property name="vorwahl" type="string">
13
<column length="10" name="abt_vorwahltel1">
14
<comment>Vorwahl 1. Telefon</comment>
15
</column>
16
</property>
17
<property name="rufnummer" type="string">
18
<column length="15" name="abt_rufnrtel1">
19
<comment>Rufnummer 1. Telefon</comment>
20
</column>
21
</property>
22 </component>
23
24 <!-...und drei weitere Component Mappings...-->
Listing 5.8: Component Mapping eines Wertobjekts
Diese Strategie des Component-Mappings wurde in allen Entitäten, welche Telefonkontaktspalten besitzen, verwendet.
5.7
Criteria
Wie bereits erwähnt, werden in den Webformularen häufig Dropdownfelder benötigt,
welche alle möglichen Benutzereingaben beinhalten. Hierzu wird eine zentrale Klasse verwendet, welche Collections vom Typ SelectItem über entsprechende Getter
zurückliefert.
5.8 Identität
113
Beispielsweise liefert folgende Methode alle möglichen Titel:
1 public synchronized List<SelectItem> getTitel() {
2
if (titel == null) {
3
Session session = getSessionFactory().openSession();
4
try {
5
List<Titel> titelList = session.createCriteria(Titel.class). ←list();
6
titel = new ArrayList<SelectItem>();
7
for (Titel t : titelList)
8
titel.add(new SelectItem(t.getTitId(), t.getTitKurzbez()));
9
} catch (HibernateException ex) {
10
//Fehlerbehandlung
11
} finally {
12
if (session != null && session.isOpen())
13
session.close();
14
}
15
}
16
return titel;
17 }
Listing 5.9: Collections moeglicher Eingaben in Dropdownfeldern
Dabei wird eine Criteria verwendet. In Zeile 3 wird eine Session geöffnet und in
Zeile 5 eine Datenbankabfrage erzeugt. Diese liefert alle Datensätze der Tabelle titel.
Die Session wird im finally-Block geschlossen.
5.8
Identität
Die Datenbank von SAS III verwendet in den meisten Fällen synthetische Primärschlüssel.
Dabei wird die Erzeugungsstrategie Sequence verwendet. Dies ist eine einfache und
zuverlässige Art, Primärschlüssel zu beziehen.
Nachteil dieser Variante ist, dass sie nicht von jeder Datenbank unterstützt wird. Bei
der Verwendung einer anderen Datenbank ist es jedoch kein großer Aufwand, die Vergabestrategie zu ändern.
1 <id name="gegId" type="java.lang.Integer">
2
<column name="geg_id"/>
3
<generator class="sequence">
4
<param name="sequence">gegenstand_geg_id_seq</param>
5.8 Identität
114
5
</generator>
6 </id>
Listing 5.10: Vergabestrategie bei synthetischem Primaerschluessel in SAS III
Der Entwickler muss sich im Projekt SAS III somit in den meisten Fällen nicht um die
Vergabe des Primärschlüssels einer Entität kümmern.
Kapitel 6
Konklusion/Ausblick
Bei der Implementierung der Softwarearchitektur zum Projekt SAS III hat sich Hibernate als sehr gutes OR-Mapping Framework bewiesen. Das in Hibernate gesetzte Vertrauen wurde bei der Implementierung vollends erfüllt.
Ein wichtiger Faktor war dabei, das Hibernate auf Standards aus der Java-Welt setzt.
Ersichtlich wird dies beispielsweise beim von Hibernate verwendetem Grundelement
der Datenpersistierung: Dem POJO.
Hier ist aber wohl auch ein anderer Schluss möglich. Hibernate setzt selbst Standards.
Schließlich war Hibernate bei der Entwicklung der EJB 3.0-Spezifikation maßgeblich
beteiligt und die Konzepte von Hibernate nahmen auf diese Entwicklung großen Einfluss.
Wie auch immer geht Hibernate bei beiden Interpretationsmöglichkeiten konform mit
Standards um. Dies stellt einen großen Vorteil dieses OR-Mapping Frameworks im
Vergleich zu anderen Frameworks dar.
Hibernate wird seit 2001 entwickelt. Es unterstützt nahezu alle Konzepte relationaler
Datenbanken. Hibernate ist Teil der JBoss-Gruppe und wird laufend weiterentwickelt.
Die Gefahr eines Entwicklungsstops von Hibernate ist gering.
Ein weiterer großer Vorteil von Hibernate ist, dass es neben der OR-Mapping Komponente auch zusätzliche Komponenten anbietet. Dies sind im Moment Hibernate
Search, Hibernate Validator und Hibernate Shards. In den OR-Mappings von Hibernate können diese Komponenten konfiguriert werden. So ist beispielsweise die Volltextsuche, die Validierung und die Verwendung verteilter Datenbanken schnell und einfach
zu implementieren.
6 Konklusion/Ausblick
116
Hibernate hat sich als de facto Standard in der Java-Welt etabliert. Hibernate ist flexibel, robust und transparent. Der Einsatz eines OR-Mapping-Frameworks ist in großen
Softwareprojekten unerlässlich.
Hibernate hat sich im Projekt SAS III als exzellentes OR-Mapping-Framework erwiesen.
Abbildungsverzeichnis
3.1 Hibernate Ueberblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
3.2 POJO - Mapping . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
3.3 Vererbungshierarchie von Collections und Maps . . . . . . . . . . . . .
30
3.4 binaere/reflexive Beziehungen . . . . . . . . . . . . . . . . . . . . . . .
34
3.5 1:n-Beziehung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
36
3.6 n:1-Beziehung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
3.7 1:1-Beziehung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
38
3.8 m:n-Beziehung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
3.9 Vererbungshierarchie Gegenstand . . . . . . . . . . . . . . . . . . . . .
41
3.10 Lebenszyklus von Entitaeten . . . . . . . . . . . . . . . . . . . . . . . .
55
3.11 HQL - Joins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
5.1 Tabellenstruktur ohne Views . . . . . . . . . . . . . . . . . . . . . . . . .
94
5.2 Dropdownfeld auf einem Webformular . . . . . . . . . . . . . . . . . . .
95
5.3 Views anstelle von Tabellen . . . . . . . . . . . . . . . . . . . . . . . . .
95
5.4 Navigationsbaum der SAS III-Webapplikation . . . . . . . . . . . . . . .
96
5.5 Datenbankverbindung beim Mappinggenerator herstellen . . . . . . . .
99
5.6 Auswahl der Datenbank . . . . . . . . . . . . . . . . . . . . . . . . . . .
99
ABBILDUNGSVERZEICHNIS
118
5.7 Hauptfenster des Mappinggenerators . . . . . . . . . . . . . . . . . . . 101
5.8 Fehlermeldung wenn kein Primaerschlüssel ausgewaehlt wurde . . . . 102
5.9 Anlegen einer Beziehung . . . . . . . . . . . . . . . . . . . . . . . . . . 104
5.10 Speicherort festlegen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.11 Vorausgefuelltes Hauptfenster beim Mappen von Tabellen . . . . . . . . 106
5.12 Webformular von SAS III . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Tabellenverzeichnis
3.1 Wichtige Session Methoden . . . . . . . . . . . . . . . . . . . . . . . . .
53
3.2 Konfiguration der JNDI-Datasource . . . . . . . . . . . . . . . . . . . . .
56
3.3 Logisches Lost Update Problem . . . . . . . . . . . . . . . . . . . . . .
60
3.4 Dirty Read
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
3.5 Nonrepeatable Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
3.6 Phantom Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
3.7 Fehlerfaelle der Isolationsgrade . . . . . . . . . . . . . . . . . . . . . . .
62
3.8 Wichtige HQL Expressions . . . . . . . . . . . . . . . . . . . . . . . . .
69
Listings
1.1 m:n-Beziehung in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
3.1 Klasse Gegenstand . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
3.2 Gegenstand Mapping File . . . . . . . . . . . . . . . . . . . . . . . . . .
18
3.3 Annotation Component-Mapping . . . . . . . . . . . . . . . . . . . . . .
22
3.4 XML Component-Mapping . . . . . . . . . . . . . . . . . . . . . . . . . .
23
3.5 Zusaetzliche Identifikationsklasse beim fachlichen Primaerschluessel .
27
3.6 Persistenzklasse beim Fachlichen Primaerschluessel . . . . . . . . . . .
28
3.7 Fachlicher Primaerschluessel in der Mapping-Datei . . . . . . . . . . . .
29
3.8 Annotation-Mapping des fachlichen Primaerschluessels . . . . . . . . .
29
3.9 XML-Mapping bei einer 1:n-Beziehung . . . . . . . . . . . . . . . . . . .
36
3.10 XML-Mapping bei einer n:1-Beziehung . . . . . . . . . . . . . . . . . . .
37
3.11 XML-Mapping einer m:n-Beziehung . . . . . . . . . . . . . . . . . . . .
39
3.12 Annotation-Mapping einer m:n-Beziehung . . . . . . . . . . . . . . . . .
40
3.13 Implementierung der Vererbungshierarchie Gegenstand . . . . . . . . .
41
3.14 Tabelle pro Klassenhierarchie . . . . . . . . . . . . . . . . . . . . . . . .
42
3.15 Tabelle pro Subklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
3.16 Tabelle pro konkreter Klasse . . . . . . . . . . . . . . . . . . . . . . . .
46
3.17 Die Konfigurationsdatei hibernate.properties . . . . . . . . . . . . . . . .
48
LISTINGS
121
3.18 Die Konfigurationsdatei hibernate.cfg.xml . . . . . . . . . . . . . . . . .
49
3.19 Setzen der Konfiguration im Quellcode . . . . . . . . . . . . . . . . . . .
51
3.20 Session und Transaktionen . . . . . . . . . . . . . . . . . . . . . . . . .
58
3.21 Expliziter HQL-Join . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
72
3.22 Datenbankabfrage mit Criteria . . . . . . . . . . . . . . . . . . . . . . .
74
3.23 Lazy Loading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
76
4.1 Einladen einer Entitaet bei Session per Request . . . . . . . . . . . . .
84
4.2 Speichern einer Entitaet bei Session per Request . . . . . . . . . . . . .
85
4.3 Session per Conversation . . . . . . . . . . . . . . . . . . . . . . . . . .
86
5.1 Aufbau eines POJOs in SAS III . . . . . . . . . . . . . . . . . . . . . . .
91
5.2 Konfiguration des Datenbankdialekts . . . . . . . . . . . . . . . . . . . .
92
5.3 SessionFactory im Projekt SAS III . . . . . . . . . . . . . . . . . . . . .
92
5.4 Baumknotenklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
97
5.5 Klassen-Knotenpunkt im Navigationsbaum . . . . . . . . . . . . . . . .
97
5.6 Typisches Laden einer Entitaet in SAS III . . . . . . . . . . . . . . . . . 108
5.7 Typische Speichermethode in SAS III . . . . . . . . . . . . . . . . . . . 109
5.8 Component Mapping eines Wertobjekts . . . . . . . . . . . . . . . . . . 112
5.9 Collections moeglicher Eingaben in Dropdownfeldern . . . . . . . . . . 113
5.10 Vergabestrategie bei synthetischem Primaerschluessel in SAS III . . . . 113
Literaturverzeichnis
[1] Beeger, Haase, Roock, Sanitz: Hibernate, Persistenz in Java-Systemen mit Hibernate und der Java Persistence API, dpunkt.verlag, 2007
ISBN: 978-3-89864-447-1
[2] Bauer, King: Java Persistence with Hibernate, Manning, 2007
ISBN 1-932394-88-5
[3] Hien, Kehle: Hibernate und die Java Persistence API, entwickler.press, 2007
ISBN: 978-3-935042-96-3
[4] Hibernate Reference Documentation,
URL: http://docs.jboss.org/hibernate/core/3.3/reference/en/html/
Datum: 14. Mai 2010
[5] InformIT, Improving Hibernate’s Performance,
URL: http://www.informit.com/
Datum: 14. Mai 2010
[6] Kiltz Webanwendungen,
URL: http://www.kiltz.de/
Datum: 14. Mai 2010
[7] Umgang mit Sessions und Transaktionen,
URL: http://community.jboss.org/wiki/Sessionsandtransactions
Datum: 14. Mai 2010
[8] Open Session in View Pattern,
URL: http://community.jboss.org/wiki/OpenSessioninView
Datum: 14. Mai 2010
[9] JBoss,
URL: http://www.hibernate.org/
Datum: 14. Mai 2010
LITERATURVERZEICHNIS
[10] Enterprise Java Beans, Java Persistence API,
URL: http://www.jcp.org/en/jsr/detail?id=220
Datum: 14. Mai 2010
[11] PostgreSQL - Offizielles Handbuch,
URL: http://www.postgresql.org/docs/
Datum: 14. Mai 2010
123
Herunterladen