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 - GUI klient aplikacja aplikacja aplikacja, aplikacja,applet applet lub lub przeglądarka przeglądarkaWWW WWW HTTP, RMI, CORBA lub inne wywołanie JDBC JDBC serwer protokół bazy danych serwer serweraplikacji aplikacji logika biznesowa JDBC JDBC baza bazadanych danych serwer protokół bazy danych baza bazadanych 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 orders 1:N 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 ORM – ContractorManager.java public class ContractorManager { 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