Dostęp do baz danych z aplikacji J2EE
Transkrypt
Dostęp do baz danych z aplikacji J2EE
47 Dostęp do baz danych z aplikacji J2EE Marek Wojciechowski [email protected] http://www.cs.put.poznan.pl/mwojciechowski/ 48 Plan rozdziału • Źródła danych w JDBC • Java Naming and Directory Interface • Transakcje na platformie J2EE 49 Bazy danych a aplikacje J2EE • 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) 50 Nieprzenaszalny kod – JDBC 1.0 • 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(); 51 Źródła danych – od JDBC 2.0 • 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 52 Źródła danych – Przykład // 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); data-sources.xml int sal = rset.getInt(2); <data-source } class="com.evermind.sql.OrionCMTDataSource" location="jdbc/OracleDS" } catch (SQLException e) {…} connection-driver="oracle.jdbc.driver.OracleDriver" username="scott" stmt.close(); conn.close(); password="tiger" url="jdbc:oracle:thin:@host1:5521:orcl /> 53 Rodzaje źródeł danych w OC4J • 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 54 Emulowane źródła danych • 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 55 Nieemulowane źródła danych • 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 56 Natywne źródła danych • 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 • 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 nie Wymagane JTA? nie tak Wymagane 2-PC? Emulated DS Non-Emulated DS Native DS 57 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 59 JNDI InitialContext (1/2) • 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 z serwisem nazw Context ic = new InitialContext(env); DataSource ds = (DataSource) ic.lookup("jdbc/OracleDS"); ... Alternatywą dla przekazania Hashtable jest umieszczenie parametrów w pliku jndi.properties 60 JNDI InitialContext (2/2) • 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ąć) Udostępnianie zasobów i informacji przez61 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: MyDS jdbc OraDS ejb EmpEJB DeptEJB DataSource ds = (DataSource) ic.lookup("jdbc/OraDS"); • 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"); 62 Źródła danych a przenaszalność aplikacji • 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) 63 Wyszukanie źródła danych przez referencję - Przykład • Przykład dla serwletu i emulowanego źródła danych: web.xml <resource-ref> <res-ref-name>jdbc/RefOracleDS</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Application</res-auth> </resource-ref> orion-web.xml <resource-ref-mapping name="jdbc/RefOracleDS" location="jdbc/OracleDS" /> data-sources.xml Operacja lookup w kodzie serwletu <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"); 64 Transakcje na platformie J2EE • 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 65 Funkcjonalność JTA - Schemat 66 Transakcje JTA w aplikacjach J2EE • 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) • 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(); ... 67 JTA: Transakcje zarządzane przez kontener (CMT) • 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 68 69 Transakcje JDBC w aplikacjach J2EE • 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 • Serwlety / JSP: – Granice transakcji wyznaczane programowo (jawnie): JDBC lub JTA • Komponenty EJB: – Granice transakcji wyznaczane programowo – BMT (JDBC / JTA) lub deklaratywnie – CMT 70