Programmieren II Objekt

Werbung
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Programmieren II
Dr. Klaus Höppner
Hochschule Darmstadt – Sommersemester 2009
1 / 35
Objekt-relationales Mapping
Hibernate
Objekt-relationales Mapping
Hibernate
2 / 35
Objekt-relationales Mapping
Hibernate
Objekt-relationales Mapping
Mit JDBC kann Java sich mit relationalen Datenbanken
verbinden.
Hierbei entsprechen
• jeder Datensatz einer Zeile in einer Datenbanktabelle und
• die Attribute eines Datensatzes den Tabellenspalten.
Daher ist es prinzipiell möglich, eine Abbildung zwischen den
Datensätzen in einer Tabelle und klassischen Java-Objekten
(POJO – plain old Java objects herzustellen.
Eine solche Abbildung nennt man objekt-relationales Mapping
(ORM).
Handout S. 1
3 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Beispieltabelle: Customer
Die folgende Tabelle beschreibt einen Kunden mit Adresse,
wobei die ID des Kunden automatisch generiert wird
(entsprechend des SQL-Dialektes für HSQLDB):
CREATE TABLE CUSTOMER(
ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 0)
NOT NULL PRIMARY KEY,
FIRSTNAME VARCHAR(20),
LASTNAME VARCHAR(20),
STREET VARCHAR(20),
CITY VARCHAR(20)
)
4 / 35
Objekt-relationales Mapping
Hibernate
Korrespondierende Klasse Customer
public class Customer {
private Long id;
private String firstname;
private String lastname;
private String street;
private String city;
// Getter und Setter
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
...
@Override
public String toString() {
return String.format("%s %s; %s; %s",
getFirstname(), getLastname(),getStreet(), getCity());
}
}
5 / 35
Objekt-relationales Mapping
Hibernate
Verknüpfung DB-Tabelle ↔ Java-Objekt
Nun sollen Java-Objekte persistent gemacht werden, indem
Objekte vom Typ Customer mit der gleichnamigen DB-Tabelle
synchronisiert werden können.
Hierzu werden der Klasse Customer zwei neue Methoden
hinzugefügt:
fetch() Hier werden für eine Instanz von Customer (mit
gültiger ID) die Werte der Attribute per SELECT
aus der Tabelle geholt.
update() Hier werden die Werte der Attribute in die Tabelle
geschrieben, wobei bei leerer ID (null) ein neuer
Datensatz per INSERT eingefügt, andernfalls die
Feldwerte des existierenden Datensatzes per
UPDATE aktualisiert werden.
Handout S. 2
6 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Klasse DBConnection
public class DBConnection {
private static Connection con = null;
public static Connection getConnection() {
if (con==null) {
try {
Class.forName("org.hsqldb.jdbcDriver");
con = DriverManager.getConnection("jdbc:hsqldb:file:"+
"data/fibu;shutdown=true", "SA", "");
} catch (ClassNotFoundException e) {e.printStackTrace();}
catch (SQLException e) {e.printStackTrace();}
}
return con;
}
public static void disconnect() {
if (con!=null) {
try {con.close();}
catch (SQLException e) {e.printStackTrace();}
finally {con=null;}
}
}
}
7 / 35
Objekt-relationales Mapping
Hibernate
Methode fetch() von Customer
public void fetch() {
if (id==null) throw new RuntimeException("Object not persistent");
Connection con = DBConnection.getConnection();
try {
PreparedStatement pst =
con.prepareStatement("select * from customer where id=?");
pst.setInt(1, id.intValue());
ResultSet res = pst.executeQuery();
if (res.next()) {
setLastname(res.getString("LASTNAME"));
setFirstname(res.getString("FIRSTNAME"));
setStreet(res.getString("STREET"));
setCity(res.getString("CITY"));
} else {
setId(null); setLastname(null);
setFirstname(null); setStreet(null);
setCity(null);
}
} catch (SQLException e) {e.printStackTrace();}
}
8 / 35
Objekt-relationales Mapping
Hibernate
Methode update() von Customer
public void update() {
Connection con = DBConnection.getConnection();
PreparedStatement pst = null;
try {
if (getId()==null) {
pst = con.prepareStatement("insert into customer "+
"(FIRSTNAME, LASTNAME, STREET, CITY) values (?,?,?,?)");
} else {
pst = con.prepareStatement("update customer set "+
"FIRSTNAME=?, LASTNAME=?, STREET=?, CITY=? where id=?");
pst.setInt(5, getId().intValue());
}
pst.setString(1, getFirstname());
pst.setString(2, getLastname());
pst.setString(3, getStreet());
pst.setString(4, getCity());
pst.executeUpdate();
pst.close();
Handout S. 3
9 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Methode update() (Forts.)
if (getId()==null) {
pst = con.prepareStatement("CALL IDENTITY()");
ResultSet res = pst.executeQuery();
if (res!=null && res.next()) {
setId(new Integer(res.getInt(1)));
res.close();
}
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (pst!=null) {
try {pst.close();}
catch (SQLException e) {e.printStackTrace();}
}
}
}
10 / 35
Objekt-relationales Mapping
Hibernate
Exkurs: PreparedStatement
In der Realisierung von fetch() und update() wurde statt
Statement jeweils PreparedStatement benutzt.
Bei PreparedStatement wird beim Anlegen das Statement
bereits mit einem SQL-Befehl (i. A. mit Platzhaltern)
vorbereitet. Dieser wird von der Datenbank vorkompiliert.
Danach können die Platzhalter mit den realen Werte belegt
werden.
Vorteile:
• Bei mehrfacher Verwendung (z. B. in Schleifen) ergibt sich
ein Laufzeitgewinn, da der Befehl schon vorkompiliert ist.
• Da die Gültigkeit der Parameter überprüft wird und
SQL-Sonderzeichen (z. B. ’) geschützt werden, werden so
genannte SQL-Injections verhindert.
11 / 35
Objekt-relationales Mapping
Hibernate
Zusammenfassung
In diesem Abschnitt wurde anhand einer einfachen Tabelle
demonstriert, wie ein objekt-relationales Mapping selber
realisiert werden kann.
Der Aufwand war aber recht hoch, da individuell für die Klasse
die entsprechenden Methoden zur Synchronisation inkl. der
entsprechenden SQL-Befehle implementiert wurden. Wegen
verschiedener SQL-Dialekte der RDBMS ist die
Implementierung auch DB-spezifisch.
Der Aufwand wird noch höher, wenn Tabellen über Schlüssel
miteinander verknüpft sind.
Daher werden in der Praxis für ORM spezielle Frameworks
benutzt.
Handout S. 4
12 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Hibernate
Es existieren verschiedene Frameworks, die ein
objekt-relationales Mapping realisieren.
Eines der bekanntesten und häufig benutzten ist Hibernate
(http://www.hibernate.org).
Was ist der Vorteil eines solchen Frameworks und ORM?
Es verbindet die Vorteile der objektorientierten
Programmierung mit den Vorteilen einer Datenbank,
insbesondere der Persistenz (d. h. dass Datensätze in einem
RDBMS nicht flüchtig sind). So kann man persistente Objekte
realisieren, die sich weitgehend wie normale Java-Objekte
verwenden lassen.
13 / 35
Objekt-relationales Mapping
Hibernate
Geschichte
Hibernate wurde von der Firma JBoss entwickelt, die sich mit
Open-Source-Software im Bereich Java-Middleware,
-Applikationsservern und Message-Brokern beschäftigt.
Mittlerweile wurde JBoss (nach einem Bieterkampf mit Oracle)
von RedHat übernommen.
14 / 35
Objekt-relationales Mapping
Hibernate
Architektur
Hibernate stellt in der einfachsten Benutzungsform dem
Anwender eine Datenbank-Session zur Verfügung, die nach
Konfiguration eines Datenbanktyps und der
Datenbankparameter geöffnet wird.
Objekte können sich in drei Zuständen befinden:
transient Das Objekt ist noch nicht mit der Datenbank
verknüpft, also flüchtig.
persistent Das Objekt ist im aktuellen Kontext mit der
Datenbank verknüpft.
detached Das Objekt war mit der Datenbank verknüpft, der
Kontext existiert aber nicht mehr. Daher ist das
Objekt aktuell losgelöst.
Handout S. 5
15 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Schaubild
16 / 35
Objekt-relationales Mapping
Hibernate
Konfiguration einer Hibernate-Anwendung
Die Konfiguration von Hibernate (Datenbank, Logging etc.)
erfolgt über die Klasse org.hibernate.cfg.Configuration.
Hierbei gibt es die Möglichkeit, zur Konfiguration die Datei
hibernate.cfg.xml im Source-Verzeichnis zu verwenden
und anschließend eine Implementierung von
org.hibernate.SessionFactory zu erzeugen:
Configuration cfg = new Configuration().configure();
SessionFactory sessionFactory = cfg.buildSessionFactory();
Alternativ können Konfigurationsparameter mit der
Methode setProperty gesetzt werden:
Configuration cfg = new Configuration();
cfg.setProperty("hibernate.connection.username","sa");
...
17 / 35
Objekt-relationales Mapping
Hibernate
Beispiel einer Konfigurationsdatei
<?xml version=’1.0’ encoding=’utf-8’?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
<property name="connection.url">jdbc:hsqldb:file:data/fibu;shutdown=true</property>
<property name="connection.username">sa</property>
<property name="connection.password"></property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<!-- Enable automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<mapping resource="fibu/Customer.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Handout S. 6
18 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Grundlagen von ORM
Durch ORM werden Klassen mit Tabellen und Instanzen der
Klassen mit Datensätzen innerhalb der Tabelle verknüpft.
Damit Instanzen eindeutig Datensätzen zugeordnet werden
können, muss die Datenbanktabelle einen eindeutigen
Primärschlüssel besitzen. Dieser wird dann als
WHERE-Ausdruck in den abgesetzten SQL-Anweisungen
verwendet.
Weiterhin dient die Existenz einer Primär-ID im Objekt der
Unterscheidung, ob ein nicht persistentes Objekt transient
oder detached ist.
19 / 35
Objekt-relationales Mapping
Hibernate
Faustregeln
• Im Allgemeinen wird eine Tabelle der Datenbank einer
Java-Klasse zugeordnet.
• Diese Klasse muss einen Standardkonstruktor besitzen.
• Die Attribute der Tabelle werden auf entsprechende
Attribute der Klasse abgebildet.
• In der Klasse werden dann Getter und Setter für diese
Attribute definiert.
• Die eigentliche Zuordnung zwischen Klasse und Tabelle
erfolgt dann über eine XML-Datei, die insbesondere den
Primärschlüssel angibt, der zur Identifikation von
Elementen verwendet wird, sowie die Namen von
Tabellenspalten den Attributen der Klasse zuordnet.
20 / 35
Objekt-relationales Mapping
Hibernate
Beispiel: Datenbank Fibu
Hier nun ein Datenbankschema für eine Buchhaltung:
Invoice
ID : INTEGER
CUSTOMERID : INTEGER
TOTAL : DECIMAL(10,2)
Item
ITEM : INTEGER
INVOICEID : INTEGER
PRODUCTID : INTEGER
QUANTITY : INTEGER
COST : DECIMAL(10,2)
Customer
ID : INTEGER
FIRSTNAME : VARCHAR(20)
LASTNAME : VARCHAR(20)
PRODUCT
ID : INTEGER
NAME : VARCHAR(20)
PRICE : DECIMAL(10,2)
Handout S. 7
21 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Erster Ansatz: Klasse Invoice
public class Invoice {
private Long id;
private Long customerID;
private Double total;
public Invoice() {}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public Long getCustomerID() { return customerID; }
public void setCustomerID(Long customerID) {
this.customerID = customerID;
}
public Double getTotal() { return total; }
public void setTotal(Double total) {
this.total = total;
}
}
22 / 35
Objekt-relationales Mapping
Hibernate
Mapping
Das eigentliche Mapping erfolgt dann über die
Datei Invoice.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="fibu">
<class name="Invoice" table="Invoice">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="customerID"></property>
<property name="total"></property>
</class>
</hibernate-mapping>
23 / 35
Objekt-relationales Mapping
Hibernate
Analyse
Die gezeigte XML-Datei enthält folgende Definitionen:
• Das Mapping wird für Klassen innerhalb des Pakets fibu
durchgeführt.
• Für jede abzubildende Klasse gibt es ein Element class,
in diesem Fall für die Klasse Invoice, gleichzeitig wird
erklärt, welcher Tabelle die Klasse zugeordnet ist.
• In diesem Fall wird für den Primärschlüssel das Attribut id
der Java-Klasse mit dem gleichnamigen Attribut der
Tabelle verknüpft.
• Weiterhin erfolgen die Abbildungen für customerID und
total.
Handout S. 8
24 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Zweiter Ansatz für Invoice
In Wirklichkeit möchte man in der Klasse Invoice statt der
reinen Kundennr. lieber direkt eine Referenz auf den Kunden
haben:
public class Invoice {
private Long id;
private Customer customer;
private Double total;
public Invoice() {
}
// Getter und Setter
}
25 / 35
Objekt-relationales Mapping
Hibernate
Mapping
Dies ist ein Fall einer Zuordnung many-to-one: Mehrere
Rechnungen können für denselben Kunden ausgestellt sein.
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="fibu">
<class name="Invoice" table="Invoice">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="total"></property>
<many-to-one name="customer">
<column name="customerID"></column>
</many-to-one>
</class>
26 / 35
Objekt-relationales Mapping
Hibernate
Mapping (Forts.)
<class name="Customer" table="Customer">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="firstname"></property>
<property name="lastname"></property>
<property name="street"></property>
<property name="city"></property>
</class>
</hibernate-mapping>
Die Zuordnung des Kunden zur Rechnung erfolgt dabei durch
die Angabe der Spalte in der Tabelle Invoice, in der sich der
Primärschlüssel des betreffenden Kunden befindet.
Handout S. 9
27 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Mapping der Rechnungspositionen
Einer Rechnung sind in der Regel viele Rechnungsposten
zugeordnet. Die Zuordnung erfolgt durch die
Spalte invoiceId in der Tabelle Item.
Hierfür wird in der Klasse Invoice nun eine Liste von Item neu
hinzugefügt, die auf den Mapping-Typ list abgebildet wird.
Hierbei wird in der Abbildung die Spalte invoiceID als key
verwendet, anhand dessen die passenden Rechnungsposten
ausgewählt werden.
Zusätzlich wird direkt die Tabelle Product anhand der
Produktnr. des Rechnunsposten in die Klasse Item abgebildet.
28 / 35
Objekt-relationales Mapping
Hibernate
Klasse Invoice
public class Invoice {
private Long id;
private Customer customer;
private List items = new ArrayList();
private Double total;
public Invoice() {
}
// Getter und Setter
...
public List getItems() {
return items;
}
public void setItems(List items) {
this.items = items;
}
}
29 / 35
Objekt-relationales Mapping
Hibernate
Klasse Item
public class Item {
private Product product;
private Double cost;
private Long quantity;
public Item() {}
// Getter und Setter
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
...
}
Handout S. 10
30 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Klasse Customer
public class Customer {
private Long id;
private String firstname;
private String lastname;
private String street;
private String city;
public Customer() {}
// Getter und Setter
...
@Override
public String toString() {
return String.format("%s %s; %s; %s",
getFirstname(), getLastname(),
getStreet(), getCity());
}
}
31 / 35
Objekt-relationales Mapping
Hibernate
Klasse Product
public class Product {
private Long id;
private String name;
private Double price;
public Product() {}
// Getter und Setter
public Long getId() { return id; }
public void setId(Long id) {
this.id = id;
}
...
}
32 / 35
Objekt-relationales Mapping
Hibernate
Mapping
<hibernate-mapping package="fibu">
<class name="Invoice" table="Invoice">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="total"></property>
<many-to-one name="customer">
<column name="customerID"></column>
</many-to-one>
<list name="items" table="item">
<key column="invoiceID"></key>
<list-index column="item"></list-index>
<composite-element class="Item">
<property name="quantity"></property>
<property name="cost"></property>
<many-to-one name="product" column="productID"/>
</composite-element>
</list>
</class>
Handout S. 11
33 / 35
Objekt-relationales Mapping
Hibernate
Vorlesung 12
Mapping (Forts.)
<class name="Customer" table="Customer">
<id name="id" column="id">
<generator class="native"></generator>
</id>
<property name="firstname"></property>
<property name="lastname"></property>
<property name="street"></property>
<property name="city"></property>
</class>
<class name="Product" table="Product">
<id name="id"></id>
<property name="name"></property>
<property name="price"></property>
</class>
</hibernate-mapping>
34 / 35
Objekt-relationales Mapping
Hibernate
Anwendung
Session session = HibernateUtil.getSessionFactory()
.getCurrentSession();
session.beginTransaction();
Invoice i = (Invoice) session.get(Invoice.class, 3L);
System.out.println(i.getCustomer());
for (Object o : i.getItems()) {
final Item it = (Item) o;
System.out.println(it.getCost());
System.out.println(it.getProduct().getName());
}
Customer c = (Customer) session.get(Customer.class, 6L);
i.setCustomer(c);
final Item it = (Item) i.getItems().get(0);
it.setQuantity(5L);
session.getTransaction().commit();
35 / 35
Handout S. 12
Herunterladen