Java i bazy danych 1. JDBC podstawy, transakcje. 2. Mapowanie relacyjno obiektowe. Hibernate, przykład. 1
JDBC - wprowadzenie Java Database Connectivity (JDBC) to specyfkacja określająca zbiór klas i interfejsów napisanych w Javie, które mogą być wykorzystane przez programistów tworzących oprogramowanie korzystające z baz danych. Implementacja JDBC jest dostarczany przez producentów baz danych. Jedną z ważniejszych zalet takiego rozwiązania jest ukrycie przed programistą kwestii technicznych dotyczących komunikacji z bazą danych. Dzięki temu ten sam program napisany w Javie może współpracować z różnymi systemami baz danych. http://docs.oracle.com/javase/tutorial/jdbc/index.html 2
JDBC połączenie // zaladowanie klasy z driverem. opcjonalne stworzenie obiektu drivera Class.forName("com.mysql.jdbc.Driver").newInstance(); // utworzenie obiektu java.sql.connection Connection con = DriverManager.getConnection( "jdbc:mysql://localhost/test?user=monty&password="); Nazwa klasy ze sterownikiem jak również postać argumentu metody getconnection() jest uzależniona od używanego RDBMS'a i opisana w jego dokumentacji (tutaj przykład dla MySQL). Alternatywa: źródło danych DataSource i usługa JNDI: Context ctx = new InitialContext(); DataSource ds = (DataSource)ctx.lookup("jdbc/MojaDB"); Connection con = ds.getconnection("monty", ""); 3
JDBC polecenie Statement stmt = con.createstatement(); Obiekt java.sql.statement reprezentuje polecenie. Istnieją jego rozszerzenia, reprezentujące polecenie z argumentami (PreparedStatement) czy też wywołanie procedury bazodanowej (CallableStatement). 4
JDBC polecenie Statement stmt = con.createstatement(); Obiekt java.sql.statement reprezentuje polecenie. Istnieją jego rozszerzenia, reprezentujące polecenie z argumentami (PreparedStatement) czy też wywołanie procedury bazodanowej (CallableStatement). 5
JDBC zapytanie ResultSet rs = stmt.executequery("select * FROM tabela"); Obiekt java.sql.resultset umożliwia dostęp do wyników zapytania (typu SELECT) wysłanego do bazy danych. while (rs.next()) { System.out.println(rs.getString("kolumna1")); W ramach jednego polecenia (java.sql.statement) można wykonać wiele zapytań różnorodnego typu (SELECT, UPDATE, DELETE,...). Dostęp do statusu realizacji zapytań (błędy, ilość zmienionych danych, wygenerowane klucze automatyczne) jest dostępny za pomocą instancji java.sql.statement. 6
JDBC zwolnienie zasobów rs.close() stmt.close(); con.close(); Używane obiekty, jeśli nie będą już wykorzystywane, należy zwalniać metodą close(). Metody dostępu do baz danych mogą zwracać wyjątek (java.sql.sqlexception), który musi zostać odpowiednio obsłużony. 7
JDBC modele komunikacji klient serwer aplikacja aplikacja JDBC JDBC baza baza danych danych protokół bazy danych klient - GUI aplikacja, aplikacja, applet applet lub lub przeglądarka przeglądarka WWW WWW serwer serwer aplikacji aplikacji serwer JDBC JDBC HTTP, RMI, CORBA lub inne wywołanie protokół bazy danych logika biznesowa baza baza danych danych 8
JDBC transakcje Transakcje to zbiór operacji zgrupowanych w jednym lub wielu obiektach Statement. Aby zakończyć transakcję należy wywołać metodę commit() na rzecz obiektu Connection. Domyślnie metoda commit() jest wywoływana po zakończeniu wykonywania zapytań w ramach jednego obiektu Statement. Aby to zmienić należy użyć metody setautocommit(false). Do anulowania zmian wprowadzonych przez niezatwierdzoną transakcję służy metoda rollback(). 9
JDBC poziomy izolacji Zwykle w systemy baz danych realizują jednocześnie wiele transakcji. Aby zapewnić kontrolę nad tym procesem wprowadzono tzw. poziomy izolacji, poprzez które określa się zasady równoległej realizacji kilku transakcji. JDBC przewiduje pięć poziomów izolacji: TRANSACTION_NONE brak transakcji. TRANSACTION_READ_UNCOMMITTED dopuszcza odczyt danych przed wywołaniem metody commit(). TRANSACTION_READ_COMMITTED inne transakcje nie mogą odczytywać zmienionych wierszy przed wywołaniem metody commit() (dirty reads). 10
JDBC poziomy izolacji TRANSACTION_REPEATABLE_READ dodatkowo chroni przed sytuacją gdy transakcja odczytuje wiersz, druga transakcja go zmienia a pierwsza ponownie go odczytuje otrzymując inne dane (non-repetable reads). TRANSACTION_SERIALIZABLE dodatkowo chroni przed sytuacją, gdy jedna transakcja odczytuje zbiór wierszy spełniający kryteria zawarte w warunku WHERE, następnie druga transakcja wstawia wiersz spełniający ten warunek, po czym pierwsza transakcja ponownie odczytuje zbiór wierszy dostając nowy rekord (phantom-read). Poziomy izolacji ustawia się metodą settransactionisolation() wywołaną na rzecz obiektu klasy Connection. 11
JDBC etapy transakcji Obiekt Savepoint (JDBC 3.0) umożliwia częściowe odwrócenie (rollback) transakcji zamiast całkowitego. Do utworzenia tego obiektu służy metoda setsavepoint(). Statement stmt = con.createstatement(); int rows = stmt.executeupdate("insert INTO AUTHORS VALUES " + "(LAST, FIRST, HOME) 'TOLSTOY', 'LEO', 'RUSSIA'"); Savepoint save1 = con.setsavepoint("savepoint_1"); int rows = stmt.executeupdate("insert INTO AUTHORS VALUES " + "(LAST, FIRST, HOME) 'MELVOY', 'HAROLD', 'FOOLAND'");... con.rollback(save1);... con.commit(); 12
Mapowanie relacyjnoobiektowe Mapowanie relacyjno-obiektowe (ORM) umożliwia zastąpienie bezpośrednich operacji na bazie danych operacjami na obiektach javy, reprezentującymi te dane. Operacje bazodanowe, związane z np. z wyszukiwaniem czy aktualizacją tych obiektów odbywają się (pół)automatycznie. Najpopularniejszą implementację takiego mapowania dla języka Java udostępnia biblioteka Hibernate ( http://www.hibernate.org). 13
ORM - przykład contractors con_id con_name con_address 1:N orders odr_id odr_date 14
ORM struktura rzykładu src data Contractor.java ContractorManager.java Contractor.hbm.xml Order.java OrderManager.java Order.hbm.xml util HibernateUtil.java Example.java hibermate.cfg.xml log4j.properties 15
ORM Contractor.java package data; import java.util.hashset; import java.util.set; public class Contractor { private int id; private String name; private String address; private Set orders = new HashSet(); public Contractor(){ // pusty konstruktor public void setid(int id) { this.id = id; public int getid() { return this.id; 16
ORM Contractor.java public void setname(string name) { this.name = name; public String getname() { return this.name; public void setaddress(string address) { this.address = address; public String getaddress() { return this.address; public void setorders(set orders) { this.orders = orders; public Set getorders() { return this.orders; 17
ORM Contractor.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> <class name="data.contractor" table="contractors"> <id name="id" column="con_id"> <generator class="native"/> </id> <property name="name" column="con_name"/> <property name="address" column="con_address"/> <set name="orders" inverse="true"> <key column="con_id"/> <one-to-many class="data.order"/> </set> </class> </hibernate-mapping> 18
ORM Order.java... public class Order { private int id; private Date date; private Contractor contractor; public Order(){ // pusty konstruktor public void setid(int id) { this.id = id;... public int getid() { return this.id; 19
ORM Order.hbm.xml... <hibernate-mapping> <class name="data.order" table="orders"> <id name="id" column="odr_id"> <generator class="native"/> </id> <property name="date" type="date" column="odr_date"/> <many-to-one name="contractor" column="con_id" class="data.contractor" /> </class> </hibernate-mapping> 20
public class ContractorManager { ORM ContractorManager.java public void create(string sname, String saddress) { Session session = HibernateUtil.getSessionFactory(). getcurrentsession(); session.begintransaction(); Contractor c = new Contractor(); c.setname(sname); c.setaddress(saddress); session.save(c); session.gettransaction().commit(); public Contractor load(int id) { Session session = HibernateUtil.getSessionFactory(). getcurrentsession(); session.begintransaction(); Contractor c = (Contractor) session.load( Contractor.class, new Integer(id)); session.gettransaction().commit(); return c; 21
ORM HibernateUtil.java package util; import org.hibernate.sessionfactory; import org.hibernate.cfg.configuration; public class HibernateUtil { private static final SessionFactory sessionfactory; static { try { // inicjalizacja na podstawie konfiguracji z pliku // hibernate.cfg.xml sessionfactory = new Configuration().configure(). buildsessionfactory(); catch (Throwable ex) { System.err.println("failure " + ex); throw new ExceptionInInitializerError(ex); public static SessionFactory getsessionfactory() { return sessionfactory; 22
ORM - struktura rzykładu src data Contractor.java ContractorManager.java Contractor.hbm.xml Order.java OrderManager.java Order.hbm.xml util HibernateUtil.java Example.java hibermate.cfg.xml log4j.properties 23
ORM - Example.java public class Example { public static void main(string[] args) { ContractorManager cm = new ContractorManager(); if (args[0].equals("create")) { Contractor c1, c2; cm.create("adam", "Lesna 11/3"); cm.create("tomasz", "Zielona 123/65"); cm.create("pawel", "Krotka 6"); c1 = cm.load(1); c2 = cm.load(2); OrderManager om = new OrderManager(); om.create(c1); om.create(c2); om.create(c1); 24
ORM - Example.java else if (args[0].equals("print")) { Order o; Session session = HibernateUtil.getSessionFactory().GetCurrentSession(); session.begintransaction(); List l = session.createquery("from Contractor").list(); for (int i = 0; i < l.size(); i++) { Contractor c = (Contractor) l.get(i); System.out.println("Contractor:"); System.out.println(c.getId() + ", " + c.getname()) + ", " + c.getaddress()); for (Iterator it=c.getorders().iterator(); it.hasnext();){ o = (Order) it.next(); System.out.println("Order:"+o.getId()+", "+o.getdate()); session.gettransaction().commit(); HibernateUtil.getSessionFactory().close(); 25
ORM - Hibernate.cfg.xml <?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> <property name="connection.driver_class"> com.mysql.jdbc.driver </property> <property name="connection.url"> jdbc:mysql://localhost/example </property> <property name="connection.username"></property> <property name="connection.password"></property> <property name="connection.pool_size">1</property> 26
ORM - Hibernate.cfg.xml <property name="dialect"> org.hibernate.dialect.mysqldialect </property> <property name="current_session_context_class"> thread </property> <property name="cache.provider_class"> org.hibernate.cache.nocacheprovider </property> <property name="show_sql">true</property> <property name="hbm2ddl.auto">create</property> <mapping resource="data/contractor.hbm.xml"/> <mapping resource="data/order.hbm.xml"/> </session-factory> </hibernate-configuration> 27
ORM - uruchomienie Niezbędne biblioteki: antlr.jar asm.jar asm-attrs.jars cglib.jar commons-collections.jar commons-logging.jar dom4j.jar hibernate3.jar jta.jar log4j.jar mysql-connector (http://dev.mysql.com/downloads/connector/j/) Pierwsze uruchomienie programu spowoduje utworzenie odpowiednich tabel: java -cp [biblioteki] Example create 28
ORM - uruchomienie Drugie uruchomienie programu: z pliku hibernate.cfg.xml usuwamy element: <property name="hbm2ddl.auto">create</property> java -cp [biblioteki] Example print Hibernate: select contractor0_.con_id as con1_0_, contractor0_.con_name as con2_0_, contractor0_.con_address as con3_0_ from contractors contractor0_ Contractor: 1, Adam, Lesna 11/3 Hibernate: select orders0_.con_id as con3_1_, orders0_.odr_id as odr1_1_, orders0_.odr_id as odr1_1_0_, orders0_.odr_date as odr2_1_0_, orders0_.con_id as con3_1_0_ from orders orders0_ where orders0_.con_id=? Order: 3, 2006-04-11 Order: 1, 2006-04-11 Contractor:... 29
Podsumowanie Dostęp do baz danych jest możliwy poprzez interfejs JDBC. Aby z niego skorzystać, należy pobrać sterownik (bibliotekę jar) dostarczony przez producenta RDBMS implementującą ten inferfejs. Takie rozwiązanie zapewnia możliwie dużą niezależność aplikacji od konkretego RDBMS. Mapowanie relacyjno-obiektowe jest powszechnie wykorzystywane w aplikacjach klasy enterprise. Przy realizacji dużych projektów, znacznie upraszcza ono architekturę aplikacji, dodatkowo oddzielając ją od RDBMS. 30