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