JDBC cz. 2, HSQLDB.
Transkrypt
JDBC cz. 2, HSQLDB.
Plan wykładu 1. Zaawansowane możliwości JDBC: ● rodzaje obiektów ResultSet, ● dodatkowe możliwości obiektów ResultSet, ● zapytania prekompilowane, ● wywoływanie zdalnych procedur, ● transakcje. 2. SSL i bazy danych: przykład HSQLDB. 1 Rodzaje obiektów ResultSet Ze względu na dostęp do odebranych danych obiekty ResultSet dzielimy na: ● TYPE_FORWARD_ONLY – odbiór danych kolejno od pierwszego do ostatniego rekordu, ● TYPE_SCROLL_INSENSITIVE – dostęp do dowolnych danych, przygotowane wyniki nie zmieniają sie pod wpływem zmian w bazie. ● TYPE_SCROLL_SENSITIVE – dostęp do dowolnych danych, przygotowane wyniki zmieniają sie pod wpływem zmian w bazie. Kolejność rekordów nie musi być stała. Rodzaj dostępu zmieniamy metodą setFetchDirection(int). Do przechodzenia między rekordami służą metody: next(), previous(), last(), first(), absolute(), relative(). 2 Rodzaje obiektów ResultSet Obiekty ResultSet mogą mieć różne możliwości zmieniana odebranych danych: 1. CONCUR_READ_ONLY – dane nie mogą być zmienione poprzez metody updateXXX(). ● najwyższy poziom współbieżności (największa liczba użytkowników jednocześnie operujących na danych ● jedyna możliwość w wersji JDBC 1.0 2. CONCUR_UPDATABLE – dane mogą być zmieniane. ● zmniejszony poziom współbieżności, ● mniejsza wydajność. 3 Rodzaje obiektów ResultSet Connection con = DriverManager.getConnection( "jdbc:my_subprotocol:my_subname"); Statement stmt = con.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE, ResultSet.HOLD_CURSORS_OVER_COMMIT); stmt.setFetchSize(25); ResultSet rs = stmt.executeQuery( "SELECT col1, col2 FROM table1"); Obiekt rs udostępnia dane w dowolnej kolejności, umożliwia zmianę danych, nie jest zamykany przy zatwierdzeniu transakcji. Do bazy danych zostaje przekazana sugestia, aby dane odbierać w pakietach po 25 rekordów. 4 Inne operacje na obiektach ResultSet 1. Usuwanie rekordów: rs.first(); rs.deleteRow(); 2. Wstawianie rekordów: rs.moveToInsertRow(); rs.updateObject(1, myArray); rs.updateInt(2, 3857); rs.updateString(3, "Mysteries"); rs.insertRow(); rs.first(); 5 Inne operacje na obiektach ResultSet Uwagi do wstawiania rekordów: 1. Na wstawianym rekordzie można wywoływać metody getXXX(). Jeśli odpowiednia wartość nie została ustawiona wcześniej metodą updateXXX() wartość zwracana będzie nieokreślona. 2. Aktualizacja wartości we wstawianym rekordzie nie zmienia obiektu ResultSet. 3. metoda insertRow(), dodająca rekord do obiektu ResultSet i do bazy danych zrzuca SQLException, jeśli liczba lub typy kolumn nie zgadzają się ze specyfikacją tabeli w bazie. 4. Bieżącym rekordem jest ten, który był nim przed wywołaniem metody moveToInsertRow(). 6 Inne operacje na obiektach ResultSet Odczytywanie dużych porcji danych: Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT xdata FROM Table2"); byte [] buff = new byte[4096]; while (rs.next()) { InputStream fin = rs.getAsciiStream(1); for (;;) { int size = fin.read(buff); if (size == -1) break; // wypisanie danych System.out.write(buff, 0, size); } } 7 Prekompilowane zapytania JDBC przewiduje możliwość tworzenia prekompilowanych zapytań. Służy do tego klasa PreparedStatement wyprowadzona z klasy Statement. Przykłady: PreparedStatement pstmt = con.prepareStatement( "UPDATE table4 SET m = ? WHERE x = ?"); PreparedStatement pstmt2 = con.prepareStatement( "SELECT a, b, c FROM Table1", ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs = pstmt2.executeQuery(); Używanie prekompilowanych zapytań może zwiększyć szybkość działania programu. 8 Przekazywanie parametrów Przekazywanie parametrów: pstmt.setString(1, "Hi"); for (int i = 0; i < 10; i++) { pstmt.setInt(2, i); int rowCount = pstmt.executeUpdate(); } Od wersji JDBC 2.0 można przekazywać parametry typu SQL BLOB oraz SQL ARRAY. PreparedStatement pstmt = con.prepareStatement( "UPDATE Table3 SET Stats = ? WHERE Depts = ?"); pstmt.setBlob(1, statistics); pstmt.setArray(2, departments); 9 Przekazywanie dużych parametrów Przykład pokazuje jak przesłać zawartość pliku jako parametr wejściowy: File file = new File("/tmp/data"); int fileLength = file.length(); InputStream fin = new FileInputStream(file); PreparedStatement pstmt = con.prepareStatement( "UPDATE Table5 SET stuff = ? WHERE index = 4"); pstmt.setBinaryStream (1, fin, fileLength); pstmt.executeUpdate(); Inny sposób polega na wykorzystaniu typów BLOB i CLOB. 10 Informacje o parametrach Dodatkowe dane o parametrach prekompilowanego zapytania można uzyskać poprzez interfejs ParameterMetaData (JDBC 3.0): PreparedStatement pstmt = con.prepareStatement( "INSERT INTO QUOTAS (ID, LAST, FIRST, DEPT, QUOTA) " + "VALUES (?, ?, ?, ?, ?)"; ParameterMetaData paramInfo = pstmt.getParameterMetaData(); int numberOfParams = paramInfo.getParameterCount(); for (int i = 1; i <= numberOfParams; i++) { String dbType = paramInfo.getParameterTypeName(i); System.out.println("Param " + i " is DBMS type " + dbType); } 11 Serie prekompilowanych zapytań Podobnie jak w przypadku Statement istnieje możliwość przesłania serii zapytań. PreparedStatement pstmt = con.prepareStatement( "UPDATE Table4 SET History = ? WHERE ID = ?"); pstmt.setClob(1, clob1); pstmt.setLong(2, 350985839); pstmt.addBatch(); pstmt.setClob(1, clob2); pstmt.setLong(2, 350985840); pstmt.addBatch(); int [] updateCounts = pstmt.executeBatch(); Jeśli którekolwiek z zapytań UPDATE zwróci cokolwiek ponad liczbę zmienionych rekordów metoda executeBatch() zrzuci wyjątek. 12 Zdalne procedury Do wywoływania zdalnych procedur używa się obiektów klasy CallableStatement: CallableStatement cstmt = con.prepareCall( "{call updatePrices(?, ?)}"); cstmt.setString(1, "Colombian"); cstmt.setFloat(2, 8.49f); cstmt.addBatch(); cstmt.setString(1, "Colombian_Decaf"); cstmt.setFloat(2, 9.49f); cstmt.addBatch(); int [] updateCounts = cstmt.executeBatch(); Procedura zostanie wywołana dwukrotnie Parametry przekazywane do procedury nazywamy parametrami IN. 13 Zdalne procedury – odbieranie wyników Istnieje możliwość ustawienia parametrów przez zdalną procedurę (parametry OUT): CallableStatement cstmt = con.prepareCall( "{call getTestData(?, ?)}"); cstmt.registerOutParameter(1, java.sql.Types.TINYINT); cstmt.registerOutParameter(2, java.sql.Types.DECIMAL); ResultSet rs = cstmt.executeQuery(); // ... odczyt danych poprzez ResultSet byte x = cstmt.getByte(1); // odczyt zwracanych parametrow BigDecimal n = cstmt.getBigDecimal(2); Procedura wypełnia przekazywane parametry. 14 Zdalne procedury – odbieranie wyników Numeracja parametrów OUT: CallableStatement cstmt = con.prepareCall( "{call getTestData(25, ?)}"); cstmt.registerOutParameter(1, java.sql.Types.TINYINT); Przy numeracji uwzględniane są tylko parametry oznaczone znakiem zapytania 15 Zdalne procedury – parametry INOUT Parametry INOUT to takie, które są przekazywane do procedury a następnie modyfikowane przez wywołaną procedurę. CallableStatement cstmt = con.prepareCall( "{call reviseTotal(?)}"); cstmt.setByte(1, (byte)25); cstmt.registerOutParameter(1, java.sql.Types.TINYINT); cstmt.executeUpdate(); byte x = cstmt.getByte(1); Parametr jest najpierw ustawiany – IN a następnie rejestrowany jako OUT. Po wywołaniu procedury można odebrać jego nową wartość. Zaleca się odebranie wszystkich danych poprzez obiekt ResultSet przed odebraniem parametrów INOUT. 16 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 obiuektu 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(). 17 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). 18 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(int) wywołaną na rzecz obiektu klasy Connection. 19 Etapy transakcji - Savepoints 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(); 20 HSQLDB – SSL HSQLDB umożliwia szyfrowanie transmisji. Aby taka transmisja była możliwa zarówno serwer jak i klient muszą być odpowiednio skonfigurowane. Konfiguracja serwera: 1. Generowanie certyfikatu serwera: >keytool -genkey -alias hsqldb -keyalg RSA -validity 30 -keystore hsqlserver.store Enter keystore password: hsqldb What is your first and last name? [Unknown]: localhost ... Is CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct? [no]: yes Enter key password for <hsqldb> (RETURN if same as keystore password): 21 HSQLDB – SSL Inny sposób generowania klucza – gdy posiadamy certyfikat podpisany przez zewnętrzne Centrum Autoryzacji” openssl pkcs8 -topk8 -outform DER -in Xpvk.pem -inform PEM -out Xpvk.pk8 -nocrypt openssl x509 -in Xcert.pem -out Xcert.der -outform DER java DERImport server.store NEWALIAS Xpvk.pk8 Xcert.der UWAGA: hasło dla klucza musi być takie samo jak hasło dla server.store! 22 HSQLDB – SSL 2. Uruchomienie serwera: >java -Djavax.net.ssl.keyStorePassword=hsqldb -Djavax.net.ssl.keyStore=hsqlserver.store -cp hsqldb.jar org.hsqldb.Server [Server@13c5982]: [Thread[main,5,main]]: checkRunning(false) entered ... [Server@13c5982]: Using TLS/SSL-encrypted JDBC ... [Server@13c5982]: Startup sequence completed in 978 ms. [Server@13c5982]: 2006-03-19 10:16:28.100 HSQLDB server 1.8.0 is online 23 HSQLDB – SSL Po stronie klienta należy: 1. Uzyskać certyfikat serwera np: >keytool -export -keystore server.store -alias hsqldb -file server.cer Enter keystore password: hsqldb Certificate stored in file <server.cer> Jeśli nie mamy dostępu do server.store możemy użyć dowolnego narzędzia do połączenia się z serwerem i odebrania certyfikatu np: openssl s_client -connect host:port - zakodowany w Base64 certyfikat pojawi sie na ekranie pomiędzy liniami -----BEGIN CERTIFICATE----... ----END CERTIFICATE-----. Ten fragment zapisujemy do pliku server.cer. 24 HSQLDB – SSL 2. Dodać certyfikat serwera jako zaufany: >keytool -import -trustcacerts -keystore hsqlclient.store -alias hsql -file hsqlserver.cer Enter keystore password: hsqldb Owner: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown Issuer: CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown Serial number: 441d1f43 Valid from: Sun Mar 19 10:07:15 CET 2006 until: Tue Apr 18 11:07:15 CEST 2006 Certificate fingerprints: MD5: 68:A2:33:BA:FA:4B:08:4A:E2:21:DD:E5:F6:7B:E3:8A SHA1: 59:6A:4D:66:03:C8:D6:B0:D1:4C:0B:1B:30:E2:90:0F: 88:66:EC:40 Trust this certificate? [no]: yes Certificate was added to keystore 25 HSQLDB – SSL JVM musi zostać poinformowana, ze w pliku hsqlclient.store znajdują się zaufane certyfikaty. Można to zrobić na trzy sposoby: a) plik hsqlclient.store należy dodać do katalogu z zaufanymi certyfikatami (w JDK zwykle: JAVA_HOME/jre/lib/security/cacerts), b) wywołać program kliencki z opcją: -Djavax.net.ssl.trustStore=/sciezka/do/hsqlclient.store c) w kodzie programu klienckiego użyć instrukcji: System.getProperties().put("javax.net.ssl.trustStore", "/sciezka/do/hsqlclient.store"); 26 HSQLDB – SSL Przykładowy program klienta: import java.sql.*; public class HSQLDb { public static void main(String[] args){ System.getProperties().put("javax.net.ssl.trustStore", "/sciezka/do/hsqlclient.store"); System.getProperties().put("javax.net.debug","all"); try { Class.forName("org.hsqldb.jdbcDriver").newInstance(); } catch (Exception e) { e.printStackTrace(); return; } 27 HSQLDB – SSL try { Connection con = DriverManager.getConnection( "jdbc:hsqldb:hsqls://localhost/test", "sa", ""); Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT NOW()"); rs.next(); System.out.println(rs.getString(1)); con.close(); } catch (SQLException ex) { ex.printStackTrace(); } } } } 28