Algorithmen und
Datenstrukturen II
Alexander Goesmann
Bioinformatics Resource Facility
Center for Biotechnology
Universität Bielefeld
Vorlesung Sommer 2010
Überblick
Datenspeicherung in relationalen Datenbanken
Persistente Datenspeicherung in Java mit JDBC
ORM – Objekt-relationales Mapping
Datenspeicherung mit Hibernate
Datenspeicherung in
relationalen Datenbanken
Teil I
Relationale Datenbanken
Relationale Datenbank dient zur elektronischen
Datenverwaltung in Computersystemen
Beruht auf relationalem Datenbankmodell von Edgar F.
Codd aus dem Jahr 1970
Basiert auf Relationen und Operationen darauf
(Vgl. relationale Algebra)
Verwaltung der gespeicherten Daten mit relationalem
Datenbankmanagementsystem (RDBMS)
Abfrage und Manipulation der Daten mit
Datenbanksprache SQL (Structured Query Language)
Grundlegende Konzepte
Relationale Datenbank ist Sammlung von Tabellen
(Relationen) mit gespeicherten Daten
Jede Zeile (Tupel) in einer Tabelle ist ein Datensatz
(record)
Tupel besteht aus Reihe von Attributwerten (Attribute =
Eigenschaften), den Spalten der Tabelle
Relationenschema legt Anzahl und Typ der Attribute für
eine Relation fest
Eindeutige Identifizierung von Datensätzen über
Schlüssel (key)
Beispiel
Quelle: Wikipedia
Beziehungen zwischen Tabellen
Redundante Speicherung von Daten vermeiden
Verwendung eines eindeutigen Schlüssels pro Tabelle zur
Identifizierung des Datensatzes (primary key)
Verweis aus anderen Tabellen auf Daten mit Hilfe dieses
Schlüssels
Derartige Attribute werden als Fremdschlüssel (foreign key)
bezeichnet
Tabellen ohne Fremdschlüssel heißen flache Tabellen
Tabelle anlegen
Syntax:
CREATE TABLE
"
"
"
"Tabellen_Name" "("Spalte 1" "Datentyp_für_Spalte_1",
" "Spalte 2" "Datentyp_für_Spalte_2",
" ... )"
Beispiel:
CREATE TABLE Nutzer (Nutzer-ID int, "
"
"Vorname varchar(50), "
"
"
"Nachname varchar(50))
"
Daten einfügen
Syntax:
INSERT INTO <Tabellenname> "
(<Spaltenname> [, weitere Spaltennamen])
VALUES (<Wert für die erste Spalte> [, weitere Werte])"
Beispiel:
INSERT INTO Nutzer (Nutzer-ID, Vorname, Nachname) "
VALUES (13, 'Hans', 'Müller')"
Daten abfragen
Syntax:
SELECT ... FROM ... WHERE <Bedingung 1> "
[<logischer Operator> <Bedingung 2>]"
Beispiele:
Select * FROM Nutzer"
Select * FROM Nutzer WHERE Vorname = „Hans“"
Select Autor, Titel FROM Bücher WHERE Verlagsjahr > 2000 "AND Verlag != „KVG“"
Daten aktualisieren
Syntax:
UPDATE <Tabelle> SET <Name einer Spalte> = <Ausdruck aus
Spalten, Konstanten, Funktionen> [, weitere Spaltennamen = Ausdruck] WHERE <Bedingung>"
Beispiele:
UPDATE Bücher SET Datum = „20.07.2010“"
UPDATE Bücher SET Datum = Datum + 2000 WHERE Datum < 20"
Daten löschen
Syntax:
DELETE FROM <Tabelle> WHERE <Bedingung>"
Beispiele:
DELETE FROM Nutzer"
DELETE FROM Entliehen WHERE Nutzer-ID = 12"
Weiterführende Themen
Zusammengesetzte Schlüssel
Indexierung
JOINs
Komplexe Abfragen und Unterabfragen
Normalisierung & Normalformen
Optimierung
Gespeicherte Funktionen und Prozeduren
Persistente Datenspeicherung
in Java mit JDBC
Teil II
JDBC
Java Database Connectivity
Einheitliche Datenbankschnittstelle zu DBMS
verschiedener Hersteller
Java Application
JDBC
Client Machine
RDBMS proprietary protocol
RDBMS
Database Server
JDBC Treiber
Für jedes DBMS ist ein JDBC-Treiber nötig
Typ 1 – bildet die JDBC API auf andere native API ab
Typ 2 – teilweise in Java, teilweise in nativem Code
geschrieben
Typ 3 – vollständig in Java geschrieben, kommuniziert über
einen middleware-server über ein datenbankunabhängiges
Protokoll
Typ 4 – vollständig in Java geschrieben, implementiert das
jeweilige Datenbank-Protokoll
Verbindung herstellen
Treiber laden
Class.forName(„com.mysql.jdbc.Driver “)
Treiber registriert sich beim Laden selbstständig beim
DriverManager
Verbindungs-URL
jdbc:<treiber>:<dbname>[propertylist]
z.B. jdbc:mysql:testdb
DriverManager vs. DataSource
DriverManager
Treiber registrieren sich beim DriverManager
Erhält Verbindungs-URL
Optional Benutzername, Passwort
Löst den Treiber über Namen in URL auf
Beispiele
Connection con = DriverManager.getConnection
(„jdbc:derby:COFFEES“)
DriverManager.getConnection
(„jdbc:derby:COFFEES“, „user“, „password“)
DataSource
Interface
Beschreibt eine Verbindung zur Datenbank
Wird häufig außerhalb der Anwendung definiert und z.B. über
Java Naming and Directory Interface (JNDI) abgefragt (hängt
vom verwendeten Framework ab)
Beispiel
InitialContext ic = new InitialContext();
DataSource ds = ic.lookup("java:comp/env/
jdbc/myDB");
Connection con = ds.getConnection();
Programmatische DataSource
Beispiel:
ClientDataSource ds = new
org.apache.derby.jdbc.ClientDataSource();
ds.setPort(1527);
ds.setHost("localhost");
ds.setUser("APP")
ds.setPassword("APP");
Connection con = ds.getConnection();
Beispieltabelle COFFEES
COF_NAME
Colombian
SUP_ID
PRICE
SALES
TOTAL
101
01.07.99
0
0
49
01.08.99
0
0
Espresso
150
01.09.99
0
0
Colombian_Decaf
101
01.08.99
0
0
49
01.09.99
0
0
French_Roast
French_Roast_Decaf
Connection
Connection stellt die Verbindung zur Datenbank dar
Kann über Statement-Objekte SQL Code ausführen
Ergebnisse der Statements werden durch ResultSets
repräsentiert
Daten aus Tabelle lesen
Statement stmt = con.createStatement();
ResultSet srs = stmt.executeQuery("SELECT
COF_NAME, PRICE FROM COFFEES");
ResultSet repräsentiert zurückgegebene Tabelle
Methoden zum Auswählen der Zeile
Methoden zum Auslesen einer Zelle zur aktuellen Zeile
-> API
ResultSet Beispiel
ResultSet srs = stmt.executeQuery(
"SELECT COF_NAME, PRICE FROM COFFEES");
while (srs.next()) {
String name = srs.getString("COF_NAME");
float price = srs.getFloat("PRICE");
System.out.println(name + "
}
" + price);
Daten in der Tabelle ändern
Statement stmt = conn.createStatement();
ResultSet srs = stmt.executeQuery("select COF_Name
from COFFEES where price = 7.99");
srs.next();
srs.updateString("COF_NAME", "Foldgers");
srs.updateRow();
Änderungen werden erst mit updateRow() geschrieben
Abbrechen mit cancelUpdates()
Weitere Features
Daten einfügen und löschen
INSERT/DELETE Statements
Transaktionen
Es werden entweder alle Änderungen gemacht oder keine
Stored Procedures
Werden in der Datenbank ausgeführt
Vor- und Nachteile
Vorteile:
Direkter und vollständiger Zugriff auf Daten
Ausführung von beliebigem (nativem) SQL-Code
Geringer Overhead
Nachteile:
Sehr aufwändig bei großen Datenbanken
Duplikation von ähnlichen SQL-Statements
Gefahr von Fehlern durch „copy & paste“
Kein direkter Bezug zu Objektmodell im Programm
Kompatibilitätsprobleme bei Verwendung von RDBMS
spezifischem Code
ORM – Objekt-Relationales
Mapping
Teil III
Persistente Datenspeicherung
Programme arbeiten oftmals auf großen Datenbeständen
Daten werden gelesen, geändert und gespeichert
Persistenz = Verwaltung und Speicherung von Daten über die
Laufzeit eines Programms hinaus
Layerstruktur objektorientierter
Applikationen
Objektorientierte Programmierung
vs. Relationale Datenbanken
Objektorientierte Programmierung fordert Einheit von Code
und Daten
Relationale Datenbank (RDB) beinhaltet nur Verwaltung von
Daten und Aufrechterhaltung ihrer Konsistenz
Relationale Datenbanken sind nicht in der Lage komplette
Objekte (als Summe ihrer Attribute und Methoden) zu
verwalten
RDB eignet sich nur zur Speicherung der „Daten“ eines
Objektes, also des Inhalts seiner Attribute
Ansatz: Mapping, also das Abbilden von Objekten und
Attributen in Datenbank-Tabellen bzw. deren Spalten
Persistenz-Layer
Aufgabe: Bereitstellen von Methoden zum Erzeugen,
Abfragen, Verändern und Löschen von Objekten einer
Klassenhierarchie
CRUD: Create, Retrieve, Update, Delete
getter/setter-Methoden zur Abfrage und Veränderung
einzelner Attribute
Schlüssel zur Flexibilität und Geschwindigkeit ist die Art
und Weise, mit der die Struktur der Objekte auf die
relationale Datenbank gemappt werden
Umfasst auch Information über Vererbungshierarchie
Identifizierung von Objekten in
Datenbanken
Ein Objekt existiert laut dem Paradigma der ObjektOrientierten Programmierung (OOP) nur einmal
Jede Objekt-Orientierte Programmiersprache verwendet
Referenzen auf Objekte, um diese zu verwalten:
Objekt wird erzeugt, eine Referenz verwaltet, weitere
Referenzen angelegt, Methoden werden benutzt und das
Objekt wird am Ende seiner Existenz zerstört
Gleichheit von Objekten wird durch Gleichheit der
Referenz ausgedrückt
Inhaltliche Gleichheit durch Gleichheit der Werte von
Attributen
Identifizierung von Objekten in
Datenbanken
Beim Ablegen eines Objektes in einer relationalen
Datenbank muss diese Form des „Selbst“ aufrecht
erhalten werden
Objekt muss eindeutig von anderen Objekten
unterscheidbar und identifizierbar sein
Persistenz-Layer weisen Objekten eindeutige
Identifikationsnummern, genannt object ids (OID) zu
Anhand dieser Nummer kann das Objekt sich selbst,
seine Daten und assoziierte Objekte identifizieren
Identifizierung von Objekten
anhand einfacher Nummern
Fortlaufende Nummerierung der Objekte
Datenbank-Systeme liefern hierzu geeignete Hilfsmittel,
z.B. SERIAL INTEGER in PostgreSQL oder AUTOINCREMENT in
MySQL
Eigenhändige Lösung: Anlegen einer Datenbanktabelle für
Metadaten zur Verwaltung der OIDs und anderer Daten
Vorteil: Gute Übertragbarkeit auf jedes Datenbank-System
Nachteil: Zusätzlicher Aufwand zur Verwaltung der Metadaten
Um Eindeutigkeit der OID zu gewährleisten, muss jeder Zugriff
auf die Metadaten exklusiv erfolgen
Locking der Tabelle oder einzelner Spalten nicht zu umgehen
Ausbremsen des Systems durch Locking, wenn viele Clients in
kurzer Folge neue OIDs anfordern
Identifizierung von Objekten mit
zusammengesetzten Nummern
Um Performanz-Einbußen beim konkurrierenden Zugriff auf
Metadaten zu verringern, wird OID aus zwei Komponenten
zusammengesetzt (high-low-Ansatz)
Zwei Komponenten:
Sitzungszähler wird von Datenbank verwaltet und beim Starten
der Applikation durch Datenbank initialisiert
Zweite Komponente wird durch Applikation selbst vergeben
Beispiel: 32-bit-Integers (z.B. unterste 10 Bit aus internem
Zähler, restliche Bits aus Sitzungszähler) gewährleistet
Eindeutigkeit bei Verringerung der Zugriffe auf Metadaten
um Faktor 210 = 1024
Nachteil: Verschwenderischer Umgang mit OIDs (Überlauf!)
Identifizierung von Objekten mit
globalen OIDs
Einsatz (weltweit) verteilter Datenbanken erfordert
andere Mittel
OID dient hier nicht nur dem Auffinden der Daten in der
Datenbank, sondern muss es der Applikation auch
ermöglichen, Datenbank als solche zu identifizieren
Ansatz: Text-OIDs, die Hostnamen des DatenbankServers, Namen der Datenbank, Namen der Tabelle und
numerische OID enthalten
Andere Möglichkeiten: URIs und URNs
Mapping: Eine Tabelle pro
Hierarchie
Alle Klassen einer Hierarchie sowie ihre Attribute werden in
eine einzige Tabelle gemappt
Vorteile:
Hohe Performanz (nur eine Tabelle pro Anfrage)
Einfache Implementierung
Einfache Abfrage der Daten
Nachträgliches Verändern der Klassen sowie polymorphe
Abfragen von Objekten ebenfalls einfach
Nachteile:
Je nach verwendeter Klassenstruktur hoher Speicherverbrauch,
da unnötig viele Spalten pro Klasse existieren
Viele leere Datenbankfelder
Mapping: Eine Tabelle pro
konkreter Klasse
Pro Klasse eine Tabelle, die die kompletten Attribute dieser
Klasse (inklusive der Attribute evtl. vorhandener abstrakter
Oberklassen) enthält
Vorteile:
einfache Implementierung
Speicherbedarf hält sich in Grenzen
Nachteile:
Polymorphe Abfragen von Objekten, besonders auf höherer
Ebene der Klassenhierarchie, sind schwierig, da mehrere
Tabellen nacheinander abgefragt werden müssen
Veränderung von Attributen (Hinzufügen, Löschen) ist um so
aufwändiger, je höher die Klasse in der Hierarchie angesiedelt
ist
Mapping: Eine Tabelle pro Klasse
Pro Klasse wird eine Tabelle erzeugt, die jedoch nur die in
dieser Klasse definierten Attribute enthält, sowie die OID
des Objektes
Verknüpfung von Tabellen mit Hilfe der OID um die
Gesamtdaten der Attribute eines Objektes zu erhalten
Vorteile:
Beste Unterstützung für polymorphe Abfragen
Nachteile:
Höchste Anforderungen an Implementierung
Performanz kann je nach verwendeter Datenbank geringer als
bei anderen Methoden sein, da die Verknüpfung evtl. mehrerer
Tabellen einen hohen Verarbeitungsaufwand erzeugt
Mapping: Ein einfaches Beispiel
Vor- und Nachteile der MappingVerfahren
Methode
Geschwindigkeit
Polymorphismus
Speicherbedarf
Hierarchie
+
-
-
Konkrete Klasse
+
-
+
Klasse
-
+
+
Datenspeicherung mit
Hibernate
Teil IV
Was ist Hibernate?
Englisch für „Winterschlaf halten“
Persistenz- und Objekt-Relationales Mapping-Framework für
Java (mittlerweile auch für .NET)
Speicherung von Objekten mit Attributen und Methoden in
relationalen Datenbanken
Zugriff auf Daten mit eigener Abfragesprache (HQL)
-
Dadurch: Unabhängig von gewählter Datenbank (z.B. MySQL)
Kompatibel zur Java Persistence API (JPA)
Open-Source
Verbreitung und Verwendung
Hibernate findet Verwendung in zehntausenden JavaProjekten weltweit
Etwa 25.000 angemeldete Entwickler in den Hibernate-Foren
Anzahl täglicher Download: ca. 3.000
(Quelle: Wikipedia 2010, Hibernate Framework)
POJO – Plain old Java Object
= ein „ganz normales“ Objekt
Beispiel – Die Klasse / das POJO „Region“:
public class Region() {
private String name;
private int start;
private int stop;
public int getStart () {
return start;
}
public int getStop () {
return stop;
}
public String getName () {
return name;
}
}
Primärschlüssel
Jedes Objekt benötigt zur eindeutigen Identifikation in der
Datenbank einen eindeutigen Primärschlüssel
Beispiel – Erweiterung der Klasse „Region“ um ein neues
Attribut „_id“
public class Region()
private Long _id;
{
public long get_id() {
return _id;
}
}
Alternativ hätte auch das Attribut „name“ verwendet werden
können
→ Dann müsste Eindeutigkeit aber sichergestellt sein!
Datenbanktabelle
Beispiel: Region
Feld
_id
name
start
stop
Typ
int(11)
char(255)
int(8)
int(8)
1
cg0025
1024
1258
2
cg0026
2080
1430
...
...
...
...
Objekt-Relationales Mapping
Erlaubt Abbildung der Objekt/Klassenstruktur auf ein
relationales Datenbankschema
Grundgerüst des Mappings in Hibernate mittels XML:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernatemapping-3.0.dtd">
<hibernate-mapping>
...
</hibernate-mapping>
Alternativ möglich: Beschreibung des Mappings mit Hilfe von
Annotationen
Objekt-Relationales Mapping
Mapping für die Klasse Region auf die entsprechende
Datenbanktabelle:
<hibernate-mapping>
<class name="de.cebitec.Region" table="Region">
...
</class>
</hibernate-mapping>
Objekt-Relationales Mapping
Mapping für die Klasse Region auf die entsprechende
Datenbanktabelle:
<hibernate-mapping>
<class name="de.cebitec.Region" table="Region">
<property name="start" column="start"/>
<property name="stop" column="stop"/>
<property name="name" column="name"/>
</class>
</hibernate-mapping>
Objekt-Relationales Mapping
Wichtig: Definition des Primärschlüssels mit der Anweisung
diesen bei jedem neuen Objekt automatisch zu erhöhen
<hibernate-mapping>
<class name="de.cebitec.Region" table="Region">
<property name="start" column="start"/>
<property name="stop" column="stop"/>
<property name="name" column="name"/>
<id column="_id" name="_id">
<generator class="increment"/>
</id>
</class>
</hibernate-mapping>
Erweiterung der Klasse Region
Jede Region kann in einer Art „Eltern-Kind“-Relation zu
einer anderen Region stehen:
public class Region() {
...
private Region parentRegion;
...
public Region getParentRegion () {
return parentRegion;
}
}
Datenbanktabelle
Beispiel: Region erweitert um „Eltern (Parent)“ Region
Benötigt zusätzliche Spalte mit „Fremdschlüssel“
Feld
_id
name
start
stop
parent_region_id
Typ
int(11)
char(255)
int(8)
int(8)
int(11)
1
cg0025
1024
1258
2
2
cg0026
2080
1430
null
...
...
...
...
...
Objekt-Relationales Mapping
Fremdschlüssel zeigt auf „Parent“-Region
<hibernate-mapping>
<class name="ABC.Region" table="Region">
<id column="_id" name="_id">
<generator class="native"/>
</id>
<property name="start" column="start"/>
<property name="stop" column="stop"/>
<property name="name" column="name"/>
<many-to-one class="de.cebitec.Region"
column="parent_region_id"
name="parent_region"/>
</class>
</hibernate-mapping>
Datenspeicherung und Abfrage
Transaktionen
Ausführung einer oder mehrerer Operationen (z.B. Holen
und Änderung von Region-Objekten) innerhalb einer
Transaktion
ACID Eigenschaften von Transaktionen:
Atomarität: „Ganz oder gar nicht“
Konsistenz: konsistenter Datenzustand (z.B. Abbuchung von
Konto und Einzahlung auf einem anderen)
Isolation: Keine gegenseitige Beeinflussung von
Transaktionen (z.B. Reisebuchung – Sperrung eines Flugs
während Auswahl des Hotels)
Dauerhaftigkeit: Änderungen einer erfolgreich
abgeschlossenen Transaktion sind dauerhaft (persistent)
Datenspeicherung und Abfrage
Komponenten/Architektur von Hibernate:
Session
-
Repräsentiert „Konversation“ zwischen Anwender (Anwendung)
und Datenbank
-
Kapselt Verbindung zur Datenbank (JDBC-Connection)
SessionFactory
-
„Kennt“ alle Mappings für eine Datenbank
-
Dient als Factory für Session-Objekte
Optional: Zwischenspeicher für abgerufene Datenbankobjekte zur
Steigerung der Performance (Caching)
-
Hibernate Konfiguration
XML-Konfiguration einer SessionFactory
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernateconfiguration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">
com.mysql.jdbc.Driver
</property>
<property name="dialect">
org.hibernate.dialect.MySQLDialect
</property>
...
<mapping resource="de/cebitec/Region.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Beispiel: Ablauf einer
Datenbankabfrage
Erstellung der Verbindung zur Datenbank (Initialisierung der
SessionFactory mit Hibernate Konfiguration)
Öffnen einer Session über die SessionFactory
Beginn einer Transaktion
Abfrage/Speicherung von Objekten
„Commit“ der Transaktion (Erst jetzt werden Änderung/
Objekte persistent!)
Schließen der Session
Später eventuell: Öffnen einer weiteren Session...
Beispiel: Holen aller „Regions“
// Initialisierung der SessionFactory (hier nicht gezeigt)
private static SessionFacotory sessionFactory = ...
// Methode zum Holen aller Region-Objekte
(Wo ist hier die Transaktion?)
public List<Region> getRegions() {
List<Region> regions = null;
Session session = sessionFactory.openSession();
try {
regions = session.createQuery(
"select region from Region as region"
).list();
} catch (Exception ex) {
// Fehlerbehandlung
} finally {
session.close();
}
return regions;
}
Beispiel: Speicherung einer Region
// Methode zur Erstellung und Speicherung eines Region-Objekts
public Region createNewRegion(String name, int start, int stop) {
Region region = new Region();
region.setName(name);
region.setStart(start);
region.setStop(stop);
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
session.save(region);
tx.commit();
} catch (Exception ex) {
tx.rollback(); // Rollback aller Änderungen!
return null;
// besser: Ausnahme werfen!
} finally {
session.close();
}
return region;
}
Literatur
C. J. Date und H. Darwen
SQL - Der Standard. Addison-Wesley, 1997
S.W. Ambler
The Design of a Robust Persistence Layer for Relational Databases.
S.W. Ambler
Mapping Objects to Relational Databases.
S.W. Ambler
Building Object Applications that work. Cambridge University Press, 1998
http://www.agiledata.org/essays/mappingObjects.html
http://download.oracle.com/docs/cd/E17409_01/javase/tutorial/jdbc/basics/index.html
http://docs.jboss.org/hibernate/core/3.3/reference/en/html/tutorial.html
C. Bauer, G. King (2005) Hibernate in Action. Manning Publications Co.
Hibernate - JBoss Community (2010) www.hibernate.org
Vielen Dank für Eure
Aufmerksamkeit