HIBERNATE
Czyli relacyjne bazy danych
obiektowo
w Javie
Adam Buraczewski <aburacze@gmail.com>
Gliwice, 15 marca 2008 r.
2
Plan wykładu
●
Wprowadzenie do idei Object-Relational Mapping
●
Dołączanie i konfigurowanie biblioteki Hibernate
●
Podstawowe powiązanie klas i tabel bazy danych
●
Tworzenie zapytań w językach SQL, HQL
oraz za pomocą Criteria Query
●
Bardziej zaawansowane struktury danych,
polimorfizm
●
Optymalizacja
●
Różne zaawansowane mechanizmy
3
Przykładowa baza danych
●
Do celów prezentacji stworzono przykładową bazę
danych genealogicznych
●
Tabele:
–
„city” (city_id, city_name) – miejscowości
–
„address” (address_id, city_id, street, house) – adresy
–
„person” (person_id, person_name, birth_date, gender,
mother_id, father_id, address_id) – osoby z
określeniem pokrewieństwa
–
„marriage” (husband_id, wife_id, wedding_date) –
informacje o małżeństwach
4
Diagram E-R przykładowej bazy
PERSON
CITY
ADDRESS
*
1
1
*
5
Diagram przykładowej bazy
PERSON
PERSON
(mother)
PERSON
(father)
MARRIAGE
1
1
1
1
*
*
0..1
0..1
6
Cechy przykładowej bazy
●
Klucze sztuczne typu INTEGER, nadawane na
podstawie sekwencji lub inaczej numerowane
automatycznie – najwygodniejsze do stosowania z
Hibernate (warto je stosować nawet gdy podczas
normalizacji wyłania się klucz złożony)
●
Tabela „marriage” ma celowo wprowadzony klucz
złożony w celu ilustracji jego obsługi w Hibernate
7
Cechy typowej
relacyjnej bazy danych
●
Dane umieszczone w wielu znormalizowanych
tabelach (3NF)
●
Poszczególne kolumny zawierają dane o
wartościach „atomowych” (INTEGER, TEXT, DATE
itp.)
●
Powiązania za pomocą kluczy
●
Dodatkowe więzy integralności, triggery
8
Co dają relacyjne bazy danych
●
Świetnie opracowana teoria i metody
projektowania
●
Dużo gotowych „silników”, od systemów
wbudowanych do dużych klastrów serwerów
●
Znormalizowany, bogaty język SQL
●
Wydajność, indeksy, wieloużytkownikowość
●
Więzy integralności, triggery, transakcje
●
Procedury składowane
●
Gotowe narzędzia do administracji, wykonywania
kopii bezpieczeństwa itp.
9
Co dają transakcje
●
Atomicity – efekt transakcji jest zapisywany w bazie
danych w całości, albo cała transakcja jest
wycofywana
●
Consistency – po zakończeniu transakcji baza
pozostaje zawsze spójna
●
Isolation – efekt transakcji jest widziany przez
innych użytkowników dopiero po jej zatwierdzeniu
●
Durability – baza danych gwarantuje że efektu
zatwierdzonej transakcji nie da się już
podważyć/wycofać
10
Dostęp do bazy przez JDBC
●
Rozwiązanie najprostsze, działa z podstawową
wersją Java Standard Edition
●
Wyganania: kompilator Javy, oraz „sterownik”, np.
postgresql-8.3-603.jdbc3.jar
●
Uruchamianie programu:
java -Djdbc.drivers=org.postgresql.Driver
-classpath postgresql-8.3-603.jdbc3.jar
...
11
Dostęp do bazy przez JDBC
(nawiązanie połączenia)
import java.sql.*;
import java.util.Properties;
Properties p = new Properties();
p.setProperty(„user”, „użytkownik”);
p.setProperty(„password”, „hasło”);
try {
Connection dbConn = DriverManager.getConnection(
„jdbc:postgresql://komputer/baza”, p)
} catch (SQLException e) {
// Połączenie nie udało się...
}
12
Dostęp do bazy przez JDBC
(proste zapytania)
try {
dbConn.begin();
Statement s = dbConn.createStatement();
ResultSet r = s.executeQuery(
„SELECT person_name FROM person ”);
while(r.next()) {
System.out.println(
r.getString(„person_name”));
}
dbConn.commit();
} catch (SQLException e) {
dbConn.rollback();
}
dbConn.close();
13
Klasy Statement i ResultSet
●
Statement – zapytanie SQL (wymaga SQL w
dialekcie odpowiedniej bazy)
–
executeQuery(„SELECT ...”) – zapytanie zwracające
wynik
–
executeUpdate(„INSERT INTO ...”) – polecenie DML
●
ResultSet – wynik zapytania SELECT
–
next() - przechodzi do następnego wiersza, zwraca true,
gdy OK
–
getXXX(int), getXXX(String) – zwraca wynik z
bieżącego wiersza, z kolumny o danym numerze lub
nazwie (XXX = Int, Double, String, Date, Time itp.)
14
Prepared Statements
●
Pozwalają na przygotowanie polecenia SQL i
wielokrotne uruchamianie go dla różnych
parametrów (szybsze gdy obsługiwane specjalnie
przez serwer bazodanowy, np. PostgreSQL)
●
Funkcje setXXX(nr, wartość) – ustawianie wartości
parametrów
●
Uniknięcie ataków typu „SQL Injection” - sterownik
dba o odpowiednie „escape'owanie” i konwersję
parametrów
15
Prepared Statements
PreparedStatement s = dbConn.prepareStatement(
„SELECT * FROM person WHERE person_name = ?”);
s.setString(1, „Jan”);
ResultSet r = s.executeQuery();
s.setString(1, „Elżbieta”);
r = s.executeQuery();
// Poniższe polecenie zadziała bezpiecznie
s.setString(1,
„'; DELETE FROM person; SELECT '”);
r = s.executeQuery();
16
Savepoints
●
Służą do realizacji eleganckiej obsługi błędów
●
W przypadku problemu wycofanie transakcji
następuje do określonego punktu, nie trzeba
wycofywać całej transakcji
Savepoint sp = dbConn.setSavepoint();
try {
// Operacje na bazie danych
} catch (Exception e) {
dbConn.rollback(sp); throw e;
} finally {
dbConn.releaseSavepoint(sp);
}
17
Plain Old Java Objects
class Person {
private int id;
private String personName;
private Date birthDate;
private char gender;
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getPersonName() {
return personName; }
public void setPersonName(String personName) {
this.personName = personName; }
}
18
Plain Old Java Objects
●
Często wykorzystywany model budowy klasy,
zgodny ze standardem Java Beans (nadaje się do
automatycznej obsługi za pomocą pakietu
java.beans)
●
Poszczególne zmienne (id, personName itp.)
prywatne, dostępne jedynie dla funkcji klasy
●
Dostęp do zmennych za pomocą pary getter/setter
(getNazwaZmiennej/setNazwaZmiennej)
●
Dla zmiennych typu boolean można stosować
isNazwaZmiennej zamiast getNazwaZmiennej
19
Łączenie JDBC i obiektowości
●
Konieczność przepisania danych z ResultSet do
obiektu
ResultSet r = s.executeQuery();
r.next();
Person p = new Person();
p.setId(r.getInteger(„person_id”));
p.setPersonName(r.getString(„person_name”));
p.setBirthDate(r.getDate(„birth_date”));
p.setGenre(r.getChar(„genre”));
//...
20
Łączenie JDBC i obiektowości
●
Modyfikacja obiektu – konieczność sprawdzenia,
czy obiekt się zmienił i wykonania odpowiedniego
polecenia UPDATE (np. dodatkowa flaga
informująca czy w obiekcie nastąpiły zmiany)
●
Nowy obiekt, usunięcie obiektu – konieczność
wygenerowania odpowiednich poleceń INSERT lub
UPDATE
●
Problem ustalenia kolejności zapisywania danych
do bazy, gdy kilka obiektów zależy od siebie
nawzajem
21
JDBC – podsumowanie
●
Mechanizm szybki i prosty w użyciu
●
Dużo ciekawych możlwiości (savepoints, prepared
statements itp.)
●
Problemy gdy wyniki zapytań mają być przepisane
do obiektu, oraz zmiany w obiekcie mają być
naniesione w bazie danych itp.
●
Problemy gdy w bazie danych ma być zapisanych
wiele powiązanych ze sobą obiektów
22
Object-Relational Mapping
●
Automatyczna wypełnienie obiektów Java na
podstawie wyniku zapytania SQL
●
Przetworzenie zmian w obiekcie do odpowiednich
poleceń INSERT/UPDATE/DELETE
●
Rozwiązywanie problemów związanych z zapisem
i odczytem powiązanych obiektów
23
Hibernate
●
ORM
●
Możliwość zapisu zapytań w języku HQL
(Hibernate Query Language) i Criteria Query –
automatyczna translacja na odpowiedni dialekt
SQL
●
Cache dla obiektów i zapytań
●
Kontrola transakcji i reakcja na błędy
●
Dużo dodatkowych możliwości i narzędzi
24
Hibernate – podstawowe klasy
●
SessionFactory – zarządza konfiguracją, tworzy
sesje
–
openSession() - tworzy nową sesję
●
Session – opisuje połączenie z bazą danych
–
getTransaction() - tworzy obiekt transakcji
●
Transaction – opisuje transakcję
–
begin()
–
commit()
–
rollback()
25
Konfiguracja
●
Plik hibernate.cfg.xml:
<?xml version="1.0" encoding="ISO-8859-2"?>
<hibernate-configuration>
<session-factory>
<property name="connection.driver_class">
org.postgresql.Driver</property>
<property name="connection.url">
jdbc:postgresql:genealogy</property>
<property name="connection.username">
użytkownik</property>
<property name="connection.password">
hasło</property>
26
Konfiguracja
<property name="dialect">
org.hibernate.dialect.PostgreSQLDialect
</property>
<property name="show_sql">false</property>
<mapping resource=”ścieżka/do/mapy1.xml”/>
<mapping resource=”ścieżka/do/mapy2.xml”/>
<mapping resource=”ścieżka/do/mapy3.xml”/>
</session-factory>
</hibernate-configuration>
27
Mapowanie bazy danych
●
Najprościej: plik XML (zwykle „klasa.hbm.xml”):
<?xml version="1.0" encoding="ISO-8859-2"?>
<hibernate-mapping>
<class name=”Person” table=”person”>
<id name=”id” type=”integer”
column=”person_id” unsaved-value=”0”>
<generator class=”sequence”>
<param name=”sequence”>
person_seq</param>
</generator>
</id>
28
Mapowanie bazy danych
<property name="personName"
type=”string” column=”person_name”
not-null=”true” unique=”false”/>
<property name="birthDate"
type=”date” column=”birth_date”
not-null=”true” unique=”false”/>
</class>
</hibernate-mapping>
29
Zwyczajowe rozmieszczenie plików
|
|-- hibernate.cfg.xml
|-- pakiet1
| |-- Klasa1.java
| |-- Klasa1.hbm.xml
| |-- Klasa2.java
| |-- Klasa2.hbm.xml
|-- pakiet2
30
Podstawowy program
SessionFactory sf
= new Configuration().configure()
.buildSessionFactory();
Session s = sf.getCurrentSession();
Transaction t = s.getTransaction();
t.beginTransaction();
// Operacje na obiektach
t.commit();
31
Podstawowe operacje na obiektach
Person p1 = (Person) s.getObject(
Person.class, 1);
s.lock(p1, LockMode.UPDATE);
p1.setPersonName(„Antoni”);
Person p2 = new Person();
p2.setPersonName(„Zdzisław”);
s.save(p2);
s.flush();
s.evict(p1);
32
Operacje na obiektach
●
Klasa Session:
–
get(klasa, identyfikator) – zwraca obiekt lub null
–
save(obiekt) – zapisuje obiekt w bazie
–
delete(obiekt) – usuwa obiekt z bazy
–
lock(obiekt, tryb) – blokuje obiekt w bazie
●
Wykonanie save() powoduje nadanie obiektowi
identyfikatora bazodanowego (np. na podstawie
sekwencji)
33
Zapytania HQL
●
Język HQL pozwala na uniezależnienie się od
dialektu SQL:
Query q = s.createQuery(
„from Person where personName = :name”)
q.setString(„name”, „Antoni”);
q.setCacheable(true);
for (Person p : (List<Person>) q.list()) {
System.out.println(p.getBirthdayDate());
}
34
Klasa Query
●
Funkcje setXXX(), podobnie jak
PreparedStatement z JDBC
●
Można stosować :nazwa do oznaczania
parametrów zapytań
●
Funkcje:
–
list() - zwraca wynik w postaci listy obiektów
–
iterate() - zwraca iterator po wynikach (oszczędza
pamięć)
–
uniqueResult() - gdy wiemy, że wynik będzie dokładnie
jeden
35
Składnia HQL
●
from pakiet.klasa – nie ma wyliczenia kolumn
(SELECT), bo i tak wszystkie są odczytywane,
chyba że ograniczamy listę kolumn
●
Typowe operatory języka SQL (AND, OR, NOT, =,
<, >, LIKE itp.)
●
Możliwość stosowania złączeń (inner/outer join)
●
Możliwość agregacji i grupowania
●
Elementy języka Java (stałe, klasy)
●
Dodatkowe funkcje: empty(), size(), maxelement()
36
Ciąg dalszy nastąpi...
●
Uzupełnieniem prezentacji jest kod programów,
które były pokazywane na żywo podczas wykładu
●
Temat jest dopiero napoczęty – ciąg dalszy bez
wątpienia nastąpi