47 Dostęp do baz danych z aplikacji J2EE Marek Wojciechowski Marek.Wojciechowski@cs.put.poznan.pl http://www.cs.put.poznan.pl/mwojciechowski/
Plan rozdziału 48 Źródła danych w JDBC Java Naming and Directory Interface Transakcje na platformie J2EE
Bazy danych a aplikacje J2EE 49 W praktyce większość aplikacji J2EE korzysta z baz danych Bazy danych to podstawowe miejsce trwałego przechowywania danych Serwlety, JSP, EJB mogą odwoływać się do baz danych Wymagania stawiane mechanizmom komunikacji z bazami danych na platformie J2EE: przenaszalność przystosowanie do działania na różnych serwerach aplikacji, łatwość instalacji efektywność buforowanie połączeń (connection pooling/caching) funkcjonalność obsługa transakcji rozproszonych Komunikacja z bazami danych w J2EE oparta o standardy: JDBC 2.0 mechanizm źródeł danych, efektywność JTA zaawansowane mechanizmy transakcyjne (implementacja standardu XA dla aplikacji języka Java)
Nieprzenaszalny kod JDBC 1.0 50 Klasa sterownika JDBC i adres JDBC URL bazy danych zaszyte w kodzie aplikacji // rejestracja sterownika Oracle JDBC DriverManager.registerDriver(new oracle.jdbc.oracledriver()); // otwarcie połączenia z bazą poprzez DriverManager Connection conn = DriverManager.getConnection( "jdbc:oracle:thin:@host1:1521:orcl", "scott","tiger"); Statement stmt = conn.createstatement(); try { ResultSet rset = stmt.executequery("select ename, sal FROM emp"); while (rset.next()) { String ename = rset.getstring(1); int sal = rset.getint(2); } } catch (SQLException e) { } stmt.close(); conn.close();
Źródła danych od JDBC 2.0 51 Obiekty implementujące interfejs javax.sql.datasource Przenaszalny, niezależny od dostawcy mechanizm uzyskiwania połączeń z bazą danych Uzyskane połączenia są normalnymi połączeniami JDBC (w aplikacji wykorzystywane na tej samej zasadzie co przy korzystaniu z DriverManager) Źródła danych mogą oferować dodatkową funkcjonalność: Buforowanie połączeń (pooling/caching) Wsparcie dla transakcji rozproszonych Typowo definiowane poza aplikacją, dostępne przez JNDI Od JDBC 2.0 preferowany sposób uzyskiwania połączeń z bazami danych Szczególnie zalecane dla aplikacji J2EE
Źródła danych Przykład 52 // uzyskanie kontekstu nazw Context ic = new InitialContext(); // wyszukanie zródła danych przez jego logiczną nazwę (JNDI) DataSource ds = (DataSource) ic.lookup("jdbc/oracleds"); // uzyskanie połączenia z bazą danych Connection conn = ds.getconnection(); Od momentu uzyskania połączenia aplikacja korzysta z niego w sposób tradycyjny Statement stmt = conn.createstatement(); try { ResultSet rset = stmt.executequery("select ename, sal FROM emp"); while (rset.next()) { String ename = rset.getstring(1); int sal = rset.getint(2); } } catch (SQLException e) { } stmt.close(); conn.close(); data-sources.xml <data-source class="com.evermind.sql.orioncmtdatasource" location="jdbc/oracleds" connection-driver="oracle.jdbc.driver.oracledriver" username="scott" password="tiger" url="jdbc:oracle:thin:@host1:5521:orcl />
Rodzaje źródeł danych w OC4J 53 Emulowane (emulated data sources) Szybkie, "lekkie" transakcje (emulacja XA w JTA) Brak wsparcia dla transakcji rozproszonych (tylko 1-PC) Pool/cache na poziomie OC4J Nieemulowane (non-emulated data sources) Pełna obsługa transakcyjności JTA 2-Phase Commit (2-PC) dla transakcji rozproszonych Pool/cache na poziomie sterownika Oracle JDBC Natywne (native data sources) Brak wsparcia JTA Udostępniają funkcjonalność sterownika JDBC konkretnego dostawcy Pool/cache ewentualnie na poziomie sterownika JDBC
Emulowane źródła danych 54 Wprowadzone dla emulacji funkcjonalności XA w JTA dla sterowników JDBC, jej nie oferującej Również obecnie zalecane, gdy aplikacja nie wymaga obsługi 2 Phase-Commit dla transakcji rozproszonych Większa efektywność niż nieemulowane (!) data-sources.xml <data-source class="com.evermind.sql.drivermanagerdatasource" name="oracleds" location="jdbc/oraclecoreds" xa-location="oracleds" ejb-location="jdbc/oracleds" connection-driver="oracle.jdbc.driver.oracledriver" username="scott" password="tiger" url="jdbc:oracle:thin:@localhost:5521:oracle" inactivity-timeout="30" /> 3 lokalizacje wymagane Używana tylko: ejb-location
Nieemulowane źródła danych 55 Pełne wsparcie JTA, również w zakresie 2-Phase Commit Pooling/caching, rozszerzenia standardu JDBC (obecnie tylko dla Oracle'a) Zalecane przy pracy z rozproszonymi bazami danych data-sources.xml <data-source class="com.evermind.sql.orioncmtdatasource" location="jdbc/oracleds" connection-driver="oracle.jdbc.driver.oracledriver" username="scott" password="tiger" url="jdbc:oracle:thin:@localhost:5521:oracle /> Tylko jedna lokalizacja specyfikowana
Natywne źródła danych 56 Implementacje DataSource poszczególnych dostawców Mogą oferować pooling/caching i specyficzne rozszerzenia JDBC Zalecana ostrożność przy ich wykorzystaniu w OC4J OC4J nie może ich uwzględniać w globalnych transakcjach (nie wspierają JTA) data-sources.xml <data-source class="com.my.datasourceimplementationclass" name="nativeds" location="jdbc/nativeds" username="user" password="pswd" url="jdbc:mydatasourceurl" /> Tylko jedna lokalizacja specyfikowana
Wybór rodzaju źródła danych - Podsumowanie 57 Czynniki, które należy brać pod uwagę: Planowane wykorzystanie transakcji JTA (konieczne dla encyjnych EJB) Planowane transakcje rozproszone wymagające 2-Phase Commit tak Wymagane JTA? nie nie Wymagane 2-PC? tak Emulated DS Non-Emulated DS Native DS
58 JNDI: Java Naming and Directory Interface Interfejs umożliwiający aplikacjom Java korzystanie z serwisów nazw (LDAP, OID,...) W J2EE używany głównie do lokalizacji: Źródeł danych Komponentów EJB Podstawowe pojęcia związane z JNDI i serwisami nazw: Binding powiązanie nazwy z obiektem lub referencją na obiekt Context zbiór powiązań nazw z obiektami Subcontext występuje gdy nazwa w jednym kontekście jest powiązana z innym kontekstem (wykorzystującym tę samą konwencję nazw) Naming system zbiór połączonych kontekstów tego samego typu Naming service usługa oferowana przez system nazw w oparciu o konkretny interfejs Lookup operacja wyszukania obiektu w kontekście w oparciu o nazwę obiektu
JNDI InitialContext (1/2) 59 Początkowy kontekst przy przyłączaniu się aplikacji do serwisu nazw Uzyskanie z poziomu aplikacji J2EE na serwerze aplikacji (ta sama JVM): Context ic = new InitialContext(); DataSource ds = (DataSource) ic.lookup("jdbc/oracleds");... Uzyskanie z poziomu zdalnego klienta (inna JVM): Hashtable env = new Hashtable(); env.put(context.initial_context_factory, "com.evermind.server.rmi.rmiinitialcontextfactory"); env.put(context.security_principal, "admin"); env.put(context.security_credentials, "welcome"); env.put(context.provider_url, Wymagane podanie "ormi://localhost:23791/current-workspace-app"); parametrów połączenia Context ic = new InitialContext(env); z serwisem nazw DataSource ds = (DataSource) ic.lookup("jdbc/oracleds");... Alternatywą dla przekazania Hashtable jest umieszczenie parametrów w pliku jndi.properties
JNDI InitialContext (2/2) 60 Konstruktor początkowego kontekstu wykorzystuje ustawienia z jednego z 3 miejsc: Wartości właściwości systemowych (system properties) Ustawiane przez serwer OC4J Plik jndi.properties dystrybuowany z aplikacją Instancja Hashtable przekazana jawnie konstruktorowi Uzyskanie początkowego kontekstu z poziomu zdalnego klienta (inna JVM) ustawienia w jndi.properties: jndi.properties java.naming.factory.initial= com.evermind.server.rmi.rmiinitialcontextfactory java.naming.provider.url= ormi://localhost:23791/current-workspace-app java.naming.security.principal=scott java.naming.security.credentials=tiger Context ic = new InitialContext(); DataSource ds = (DataSource) ic.lookup("jdbc/oracleds");... 23791 domyślny port ormi (można pominąć)
61 Udostępnianie zasobów i informacji przez JNDI Kontener OC4J posiada swoje drzewo JNDI Inicjalizacja w oparciu o pliki konfiguracyjne serwera np. data-sources.xml Przykład ścieżki dla operacji lookup: jdbc DataSource ds = (DataSource) ic.lookup("jdbc/orads"); / ejb MyDS OraDS EmpEJB DeptEJB Każda aplikacja posiada własne drzewo JNDI Inicjalizacja w oparciu o deskryptor instalacji aplikacji np. web.xml, ejb-jar.xml (referencje na EJB, DS, zmienne środowiskowe) Ścieżka do zasobu poprzedzana prefiksem: java:comp/env/ Dla referencji na źródła danych i EJB oraz zm. środowiskowych Zawsze przy dostępie do lokalnych obiektów (w tej samej JVM) Przykład ścieżki dla operacji lookup: DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/orads");
Źródła danych a przenaszalność aplikacji 62 Aplikacja może uzyskiwać źródła danych z drzewa JNDI kontenera (OC4J) Definicja w pliku data-sources.xml kontenera lub aplikacji Lookup: DataSource ds = (DataSource) ic.lookup("jdbc/orads"); Bardziej elastyczne rozwiązanie polega na wykorzystaniu referencji do źródeł danych w drzewie JNDI aplikacji Definicja źródła w pliku data-sources.xml OC4J (nazwa JNDI źródła) Definicja referencji w deskryptorze instalacji (web.xml, ejb-jar.xml) Powiązanie referencji na źródło danych z nazwą JNDI źródła danych w specyficznym dla OC4J pliku konf. (orion-web.xml, orion-ejb-jar.xml) Przy instalacji aplikacji wymagana jedynie zmiana powiązań w plikach orion-web.xml/orion-ejb-jar.xml (dostarczonych z aplikacją lub automatycznie tworzonych przez OC4J przy instalacji aplikacji)
Wyszukanie źródła danych przez referencję - Przykład 63 Przykład dla serwletu i emulowanego źródła danych: <resource-ref> <res-ref-name>jdbc/reforacleds</res-ref-name> <res-type>javax.sql.datasource</res-type> <res-auth>application</res-auth> </resource-ref> Operacja lookup w kodzie serwletu web.xml orion-web.xml <resource-ref-mapping name="jdbc/reforacleds" location="jdbc/oracleds" /> data-sources.xml <data-source class="com.evermind.sql.drivermanagerdatasource" name="oracleds" location="jdbc/oraclecoreds" xa-location="oracleds" ejb-location="jdbc/oracleds" connection-driver="oracle.jdbc.driver.oracledriver" username="scott" password="tiger" url="jdbc:oracle:thin:@localhost:5521:oracle" /> DataSource ds = (DataSource) ic.lookup("java:comp/env/jdbc/reforacleds");
Transakcje na platformie J2EE 64 Transakcja to podstawowa logiczna jednostka interakcji użytkownika z systemem posiadająca własności "ACID": (A) atomowość, (C) spójność, (I) izolacja, (D) trwałość Transakcjom na platformie J2EE poświęcony jest standard JTA: Java Transaction API Oparty na specyfikacji JDBC 2.0 i standardzie XA Specyfikuje interfejsy między zarządcą transakcji (zgodnym z JTS Java Transaction Service) a innymi jej uczestnikami Za zarządzanie transakcjami odpowiada zarządca transakcji J2EE Niektóre typy komponentów aplikacji J2EE mogą korzystać z tradycyjnych transakcji JDBC Za zarządzanie transakcjami odpowiada zarządca transakcji SZBD
Funkcjonalność JTA - Schemat 65
Transakcje JTA w aplikacjach J2EE 66 Głównie transakcje realizowane przez komponenty EJB JTA również dostępne w serwletach/jsp Niektóre typy EJB (sesyjne, komunikatowe), podobnie jak serwlety i JSP, mogą korzystać z transakcji JDBC (dla transakcji lokalnych) Realizacja transakcji JTA w aplikacji J2EE wymaga: Rejestracji zasobów Rejestracja zasobów wiąże się z konfiguracją źródeł danych w jednym z plików konfiguracyjnych aplikacji Od liczby wykorzystanych zasobów w transakcji zależy czy użyty będzie mechanizm 2-Phase Commit Wyznaczenia granic transakcji dla EJB 2 możliwe podejścia: Przez komponent (BMT: Bean-Managed Transaction) Przez kontener (CMT: Container-Managed Transaction)
JTA: Transakcje zarządzane przez komponent (BMT) 67 Aplikacja jawnie rozpoczyna i kończy transakcję przez interfejs UserTransaction Dozwolone dla sesyjnych EJB Niedozwolone dla encyjnych EJB Jedyna możliwość wykorzystania transakcji JTA w serwletach / JSP... Context ctx = new InitialContext(); UserTransaction ut = (UserTransaction) ctx.lookup("java:comp/usertransaction"); ut.begin(); DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/oracleds"); // uzyskanie obiektu Connection po (!) rozpoczęciu transakcji Connection conn = ds.getconnection();... ut.commit();...
JTA: Transakcje zarządzane przez kontener (CMT) 68 Dostępne tylko dla komponentów EJB Dla encyjnych jedyna możliwość Dla sesyjnych wybór poprzez wartość elementu <transaction-type>: Bean/Container Transakcją steruje kontener, na podstawie informacji podanych w sposób deklaratywny w deskryptorze instalacji Deklaracje mają postać atrybutu transakcyjnego <trans-attribute> podanego dla całego komponentu lub poszczególnych jego metod Required, RequiresNew, Supports, NotSupported, Mandatory, Never
Transakcje JDBC w aplikacjach J2EE 69 Głównie z myślą o wykorzystaniu "spadkowego" kodu Dostępne dla serwletów/jsp oraz sesyjnych i komunikatowych EJB Aplikacja jawnie kończy transakcję poprzez interfejs Connection Nie ma konieczności jawnego rozpoczynania transakcji... Connection conn =...; conn.setautocommit(false); // wyłączenie trybu "auto-commit"... // polecenia SQL conn.commit(); // lub: conn.rollback();
Transakcje w aplikacjach J2EE - Podsumowanie 70 Serwlety / JSP: Granice transakcji wyznaczane programowo (jawnie): JDBC lub JTA Komponenty EJB: Granice transakcji wyznaczane programowo BMT (JDBC / JTA) lub deklaratywnie CMT