JDBC−Treiber Middleware−Spezialist OpenLink bietet universellen Treiber für die meisten DBMS (inkl. PostgreSQL) und ODBC−Datenquellen. JDBC−Treiber kontaktiert Request Broker. Request Broker vermittelt Zugriff zur Datenbank Sun Microsystems bietet mit JDBC eine JDBC−ODBC− Brücke für Zugriff auf ODBC−Datenquellen. Treiber−Klasse: sun.jdbc.odbc.JdbcOdbcDriver Weitere JDBC−Treiber: http://industry.java.sun.com/products/jdbc/drivers Web−Anwendung zur Treiberauswahl Web−Anwendungen mit Java 401 Datenbankverbindung aufbauen Connection con = DriverManager.getConnection ("jdbc:postgresql://mark:5712/eshop", "user", "password"); JDBC−URL identifiziert Datenbank. Aufbau: jdbc:subprotocol:subname Treibermanager übergibt JDBC−URL der Reihe nach an registrierte Treiber. Driver.acceptsURL(String url) Liefert true, falls der Treiber das Subprotocol erkennt. Beispiel: JDBC−ODBC−Bridge erkennt jdbc:odbc:mydb;UID=me;PWD=secret Web−Anwendungen mit Java 402 Connection Ergebnis von DriverManager.getConnection() ist eine Connection (oder eine SQLException). Verbindung zur Datenbank Connection ist teure Ressource: Aufbau der Verbindung kostet viel Zeit. Anzahl gleichzeitiger Verbindungen häufig beschränkt. Voreinstellung bei PostgreSQL: max. 32 Verbindungen Wichtigste Aufgaben: Erzeugen von Statement−Objekten Beschreibungen von Datenbank und DBMS erfragen Transaktionen handhaben Web−Anwendungen mit Java 403 Statement Statement stmt = con.createStatement(); Wird immer aus bestehender Connection erzeugt Ausführen einer SQL−Anweisung über die Connection. Mehrere parallele Statements pro Connection möglich SELECT−Anweisung ausführen: ResultSet rs = stmt.executeQuery("SELECT * FROM kunden"); Daten ändern: int updates = stmt.executeUpdate("DELETE FROM kunden ..."); Web−Anwendungen mit Java 404 ResultSet: Zeilendurchlauf ResultSet rs = stmt.executeQuery("SELECT * FROM kunden"); Ergebnis einer Selektionsanweisung: Tabelle Wird aus Statement erzeugt. ResultSet enthält einen Datensatz−Zeiger (Cursor) zum Durchlauf der Tabelle. Voreinstellung: sequentiell und lesend JDBC 2: nichtsequentielle und aktualisierbare ResultSets Zeiger steht initial vor der ersten Tabellenzeile. ResultSet.next() positioniert zur nächsten Zeile. Liefert false, falls bereits auf letzter Zeile Web−Anwendungen mit Java 405 ResultSet: Attribute lesen Spaltenwerte (Attribute) einer Zeile mit getXXX()− Methoden lesen Treiber konvertiert Daten, falls möglich Beispiel: Lesen einer ganzen Zahl in Spalte »anzahl«: Effizientere Methode, falls Spaltenindex bekannt: int n = rs.getInt("anzahl"); int n = rs.getInt(4); Spaltenindex zum Spaltennamen finden int findColumn(String columnName) Strategie: Spaltenindex einmalig ermitteln und merken, Werte danach immer über den Index abrufen. Web−Anwendungen mit Java 406 ResultSet: Attribute lesen Zahlreiche weitere Methoden getArray, getAsciiStream, getBigDecimal, getBinaryStream, getBlob, getBoolean, getByte, getBytes, getCharacterStream, getClob, getDate, getDouble, getFloat, getInt, getLong, getObject, getRef, getShort, getString, getTime, getTimestamp Web−Anwendungen mit Java 407 ResultSet: Attribute lesen Methode getObject Liest jeden beliebigen SQL−Datentyp Liefert Ergebnis als entsprechenden Java−Typ Nullwerte Temperaturen Datum Temperatur 24.12.2001 2 25.12.2001 0 26.12.2001 27.12.2001 −2 Spalte kann leere Zellen enthalten (Nullwert, SQL−NULL). Bei leerer Zelle liefert getInt() ebenfalls das Ergebnis 0. Unterscheidung zu echter 0 möglich durch wasNull() true, falls zuletzt mit getXXX() gelesene Zelle SQL−NULL enthielt false sonst Web−Anwendungen mit Java 408 ResultSet: Ressourcen freigeben Nach Bearbeitung ResultSet schließen void close() Gibt Ressourcen frei Ausführen einer weiteren Anweisung mit demselben Statement schließt das ResultSet implizit. Web−Anwendungen mit Java 409 ResultSet: positionieren und ändern Statement createStatement(int resultSetType, int resultSetConcurrency) Parameter (ResultSet−Konstanten) ermöglichen beliebiges Positionieren (»Scrolling«) und Ändern der Datensätze. TYPE_FORWARD_ONLY: sequentieller Durchlauf TYPE_SCROLL_INSENSITIVE: positionierbar, Änderungen an Datenbank werden nicht bemerkt. TYPE_SCROLL_SENSITIVE: positionierbar, Änderungen an Datenbank werden bemerkt. CONCUR_READ_ONLY: nur lesen CONCUR_UPDATABLE: Änderungen möglich Web−Anwendungen mit Java 410 ResultSet: positionieren void beforeFirst() boolean first() Positioniert auf den ersten Satz. boolean last() Positioniert vor den ersten Satz. Positioniert auf den letzten Satz void afterLast() Positioniert hinter den letzten Satz. Web−Anwendungen mit Java 411 ResultSet: positionieren boolean absolute(int pos) boolean relative(int rows) Positioniert relativ zum aktuellen Satz vorwärts (rows positiv) oder rückwärts (rows negativ). boolean next() Positioniert relativ zum Anfang (pos positiv) oder relativ zum Ende (pos negativ). Positioniert auf den nächsten Satz. boolean previous() Positioniert auf den vorigen Satz. Web−Anwendungen mit Java 412 ResultSet: positionieren int getRow() boolean isBeforeFirst() Liefert true, falls auf dem ersten Satz. boolean isLast() Liefert true, falls vor dem ersten Satz. boolean isFirst() Liefert aktuelle Satznummer. Liefert true, falls auf dem letzten Satz. boolean isAfterLast() Liefert true, falls hinter dem letzten Satz. Web−Anwendungen mit Java 413 ResultSet: Datensatz ändern Methoden updateXXX() ändern Werte in aktueller Zeile. rs.absolute(5); rs.updateString("Name", "Klute"); rs.updateInt(2, 42); rs.updateNull(3); rs.updateRow(); void updateRow() void cancelRowUpdates() Schreibt geänderte Zeile in die Datenbank. Macht Änderungen rückgängig − vor updateRow(). void deleteRow() Löscht aktuelle Zeile aus ResultSet und Datenbank Web−Anwendungen mit Java 414 ResultSet: Datensatz einfügen Einfügezeile: im ResultSet, nicht in der Datenbank rs.moveToInsertRow(); rs.updateString("Name", "Klute"); rs.updateInt(2, 42); rs.insertRow(); rs.moveToCurrentRow(); void moveToInsertRow() void insertRow() Positioniert auf die Einfügezeile Schreibt Einfügezeile in ResultSet und Datenbank void moveToCurrentRow() Positioniert von der Einfügezeile auf die aktuelle Zeile im ResultSet Web−Anwendungen mit Java 415 ResultSet: Änderungen erkennen Datenbank kann sich jederzeit ändern... ...durch andere Benutzer, ...durch eigene parallele Anweisungen. Ziel: beim Lesen des ResultSets Änderungen erkennen Voraussetzung: Statement mit ResultSet.TYPE_SCROLL_SENSITIVE erzeugen. Mehraufwand kostet Performance. Implementationsabhängig Web−Anwendungen mit Java 416 ResultSet: Änderungen erkennen void refreshRow() boolean rowDeleted() Liefert true, falls aktuelle Zeile in der Datenbank gelöscht wurde. boolean rowInserted() Liest aktuelle Zeile neu aus der Datenbank. Liefert true, falls aktuelle Zeile »Einfügung hatte«. boolean rowUpdated() Liefert true, falls aktuelle Zeile in Datenbank geändert wurde. Web−Anwendungen mit Java 417 PostgreSQL und JDBC 2 Mit dem PostgreSQL beigegebenen JDBC−Treiber ist nicht alles möglich, was JDBC 2 für ResultSets vorsieht. TYPE_FORWARD_ONLY: Ja TYPE_SCROLL_INSENSITIVE: Ja TYPE_SCROLL_SENSITIVE: Nein CONCUR_READ_ONLY: Ja CONCUR_UPDATABLE: Nein Web−Anwendungen mit Java 418 Daten ändern mit UPDATE SQL−Anweisung UPDATE ändert bestehende Datensätze. Viele Datensätze durch einen einzigen Befehl ändern Daten ändern auch ohne modifizierbare ResultSets Beispiele: UPDATE artikel SET preis = 100; UPDATE artikel SET preis = preis * 1.05 where id = 538; UPDATE artikel SET preis = preis * 1.05 where preis < 1000; Web−Anwendungen mit Java 419 Daten löschen mit DELETE SQL−Anweisung DELETE löscht bestehende Datensätze. Viele Datensätze durch einen einzigen Befehl löschen Daten löschen ohne modifizierbare ResultSets Beispiele: DELETE FROM artikel WHERE id = 538; DELETE FROM artikel WHERE preis < 100; DELETE FROM artikel WHERE NOT id IN (artikel FROM bestellungen); Löscht alle Artikel, die nie bestellt wurden Web−Anwendungen mit Java 420 ResultSet: Treiberhinweise void setFetchDirection(int direction) Optimierungshinweis an den JDBC−Treiber: In welcher Richtung liest die Anwendung das ResultSet? FETCH_FORWARD, FETCH_REVERSE oder FETCH_UNKNOWN int getFetchDirection() void setFetchSize(int rows) JDBC−Treiber muß nicht alle Zeilen sofort aus der Datenbank holen. Optimierungshinweis an den JDBC−Treiber: Wieviel Zeilen am Stück holen? int getFetchSize() Web−Anwendungen mit Java 421 ResultSet: Diverses Statement getStatement() int getType() TYPE_FORWARD_ONLY, TYPE_SCROLL_INSENSITIVE oder TYPE_SCROLL_SENSITIVE int getConcurrency() Statement, das dieses ResultSet erzeugt hat CONCUR_READ_ONLY oder CONCUR_UPDATABLE String getCursorName() Zum Ändern oder Löschen der aktuellen Zeile UPDATE table SET n = 42 WHERE CURRENT OF cursor DELETE FROM table WHERE CURRENT OF cursor Web−Anwendungen mit Java 422 ResultSet: Metadaten Informationen über das ResultSet public ResultSetMetaData getMetaData() Anzahl der Spalten Spaltentyp, −name und −titel Kann man Spalte nur lesen oder auch schreiben? Entsteht Spaltenwert durch automatische Numerierung (Sequenz)? Läßt sich Spalte in WHERE−Klausel verwenden? Darf Spalte Nullwert enthalten? usw. Web−Anwendungen mit Java 423 Statement: Daten ändern ResultSet executeQuery(String) führt SELECT−Anweisung aus. int Statement.executeUpdate(String) führt INSERT−, UPDATE− oder DELETE−Anweisung aus. Liefert Anzahl betroffener Zeilen (»Update count«). Auch für sonstige Befehle (»CREATE ...«) geeignet. boolean execute(String) führt beliebige SQL− Anweisung aus. Liefert true, falls Ergebnis ein ResultSet ist ⇒ ResultSet rs = stmt.getResultSet(); Liefert false, falls Ergebnis ein Update count ist ⇒ int count = stmt.getUpdateCount(); Web−Anwendungen mit Java 424 Statement: vorbereitete Anweisungen Aufbau der Anweisung fest, Parameter wechseln Beispiel PreparedStatement pstmt = con.prepareStatement ("INSERT INTO temperaturen VALUES (?, ?)"); pstmt.setString(1, "{d 2001−12−24}"); pstmt.setInt(2, 2); pstmt.executeUpdate(); pstmt.setString(1, "{d 2001−12−25}"); pstmt.setInt(2, 0); pstmt.executeUpdate(); pstmt.setString(1, "{d 2001−12−26}"); pstmt.setNull(2, Types.INTEGER); pstmt.executeUpdate(); pstmt.setString(1, "{d 2001−12−27}"); pstmt.setInt(2, −2); pstmt.executeUpdate(); Läßt sich auch für Queries nutzen. Web−Anwendungen mit Java 425 Statement: Stapelverarbeitung Mehrere SQL−Anweisungen in einem Rutsch ausführen (optionale JDBC−Operation) Schritt 1: Anweisungen sammeln Schritt 2: Stapel ausführen void clearBatch() void addBatch(String sql) Stapel löschen SQL−Anweisung zum Stapel hinzufügen int[] executeBatch() Stapel ausführen Ergebniscode pro Anweisung Web−Anwendungen mit Java 426 DBMS−Metadaten Connection−Methode public DatabaseMetaData getMetadata() Methoden liefern Informationen über das DBMS, SQL− Features, JDBC−Treiber, Tabellen, Datenbankprozeduren u.v.a.m. Nicht jeder JDBC−Treiber unterstützt jede Methode von DatabaseMetaData. Richtig: SQLException Falsch: Fehlende oder falsche Informationen Web−Anwendungen mit Java 427 DBMS−Metadaten int−Methoden: beschreiben technische Einschränkungen Maximale Anzahl von Spalten je Tabelle Größte Länge einer SQL−Anweisung usw. boolean−Methoden: Fähigkeit verfügbar oder nicht. Beispiele: boolean supportsANSI92EntryLevelSQL() boolean supportsANSI92FullSQL() Ergebnisse häufig als ResultSet Beispiel: unterstützte Datentypen mittels Servlet anzeigen Web−Anwendungen mit Java 428 Beispiel: DBMS−Metadaten anzeigen Web−Anwendungen mit Java 429 Beispiel: DBMS−Metadaten anzeigen Web−Anwendungen mit Java 430 Beispiel: DBMS−Metadaten anzeigen package de.rainer_klute.jdbc; import import import import java.io.*; java.sql.*; javax.servlet.*; javax.servlet.http.*; public class DBInfo extends HttpServlet { /** * <p>Initializes the servlet by setting the * jdbc.drivers system property from the * "databaseDrivers" servlet context init * parameter.</p> * * <p><strong>FIXME:</strong> The method should * append only those driver class names that are not * already in the "jdbc.drivers" property.</p> */ public void init(ServletConfig config) throws ServletException Web−Anwendungen mit Java 431 Beispiel: DBMS−Metadaten anzeigen { } final String driverProp = "jdbc.drivers"; super.init(config); StringBuffer newDrivers = new StringBuffer(); String oldDrivers = System.getProperty(driverProp); if (oldDrivers != null) { newDrivers.append(oldDrivers); newDrivers.append(’:’); } String s; if ((s = config.getServletContext(). getInitParameter ("databaseDrivers")) != null) newDrivers.append(s); System.setProperty (driverProp, newDrivers.toString()); Web−Anwendungen mit Java 432 Beispiel: DBMS−Metadaten anzeigen public void doGet(HttpServletRequest req, HttpServletResponse resp) { PrintWriter out = null; try { out = resp.getWriter(); doGetLocal(req, resp, out); } catch (SQLException ex) { out.println("<pre>"); ex.printStackTrace(out); out.println("</pre>"); } catch (IOException ex) { out.println("<pre>"); ex.printStackTrace(out); out.println("</pre>"); } } Web−Anwendungen mit Java 433 Beispiel: DBMS−Metadaten anzeigen private void doGetLocal(HttpServletRequest req, HttpServletResponse resp, PrintWriter p) throws SQLException { // Parameter für Connection ermitteln: ServletContext sc = getServletContext(); String url = sc.getInitParameter("databaseURL"); String user = sc.getInitParameter("databaseUser"); String password = sc.getInitParameter("databasePassword"); // Datenbankverbindung aufbauen: Connection con = DriverManager.getConnection (url, user, password); DatabaseMetaData dbmd = con.getMetaData(); Web−Anwendungen mit Java 434 Beispiel: DBMS−Metadaten anzeigen p.println("<div>"); p.println("<p>Database system: " + dbmd.getDatabaseProductName() + " " + dbmd.getDatabaseProductVersion() + "</p>"); p.println("<p>JDBC driver: " + dbmd.getDriverName() + " " + dbmd.getDriverVersion() + "</p>"); ResultSet rs; /* Schemas */ p.println("<p>Schemas:</p>"); rs = dbmd.getSchemas(); Util.printAsHTML(p, rs); /* Catalogs */ p.println("<p>Catalogs:</p>"); rs = dbmd.getCatalogs(); Util.printAsHTML(p, rs); Web−Anwendungen mit Java 435 Beispiel: DBMS−Metadaten anzeigen /* TableTypes */ p.println("<p>Table types:</p>"); rs = dbmd.getTableTypes(); Util.printAsHTML(p, rs); /* Tables */ p.println("<p>Tables:</p>"); rs = dbmd.getTables(null, null, null, null); Util.printAsHTML(p, rs); /* Data types */ p.println("<p>Data types supported by " + "this database system:</p>"); rs = dbmd.getTypeInfo(); Util.printAsHTML(p, rs); // Datenbankverbindung schließen: con.close(); } } p.println("</div>"); Web−Anwendungen mit Java 436 Beispiel: Klasse Util package de.rainer_klute.jdbc; import java.io.*; import java.sql.*; import java.util.*; /** * <p>Contains static methods for dealing with JDBC * objects.</p> * * @author Rainer Klute &lt;klute@rainer−klute.de&gt; */ public class Util { Web−Anwendungen mit Java 437 Beispiel: Klasse Util /** * <p>Prints the contents of a {@link ResultSet} to * a {@link PrintWriter} as an HTML table with cell * borders.</p> * * @param p The HTML table is rendered to this * {@link PrintWriter} * @param re The {@link ResultSet} to print * * @see #printAsHTML(PrintWriter, ResultSet, Map) */ public static void printAsHTML(PrintWriter p, ResultSet rs) throws SQLException { Map m = new HashMap(1); m.put("border", "1"); printAsHTML(p, rs, m); } Web−Anwendungen mit Java 438 Beispiel: Klasse Util /** * <p>Prints the contents of a {@link ResultSet} to * a {@link PrintWriter} as an HTML table.</p> * * @param p The HTML table is rendered to this * {@link PrintWriter} * @param re The {@link ResultSet} to print * @param tableAttr Attributes to use for the * HTML table * * @see #printAsHTML(PrintWriter, ResultSet) */ public static void printAsHTML(PrintWriter p, ResultSet rs, Map tableAttr) throws SQLException { p.print("<table"); /* Print table attributes */ for (Iterator i = tableAttr.keySet().iterator(); i.hasNext();) Web−Anwendungen mit Java 439 Beispiel: Klasse Util { String attr = (String) i.next(); p.print(’ ’); p.print(attr); p.print("=\""); p.print((String) tableAttr.get(attr)); p.print(’\"’); } p.println(">"); /* Get number of columns */ ResultSetMetaData rsmd = rs.getMetaData(); int columns = rsmd.getColumnCount(); /* Get column names from metadata and print them * to the first table row. */ p.print("<tr>"); for (int i = 1; i <= columns; i++) p.print("<th><p>" + rsmd.getColumnName(i) + "</p></th>"); p.println("</tr>"); Web−Anwendungen mit Java 440 Beispiel: Klasse Util } /* Print result set lines. */ while (rs.next()) { p.print("<tr>"); for (int i = 1; i <= columns; i++) { /* Print cell contents. */ String s; Object o; if ((o = rs.getObject(i)) != null) s = o.toString(); else s = "&nbsp;"; p.print("<td><p>" + s + "</p></td>"); } p.println("</tr>"); } p.println("</table>"); } Web−Anwendungen mit Java 441 Transaktionen Zusammengehörende Datenbankoperationen Entweder alle ausführen oder keine Beispiel: Überweisung int amount = 100; /* Transaktion einleiten. */ con.setAutoCommit(false); fromAccount(con, 1, amount); if (Math.random() > 0.5) throw new RuntimeException("Crash"); toAccount(con, 2, amount); /* Transaktion abschließen: Ergebnisse endgültig * in Datenbank schreiben. */ con.commit(); con.setAutoCommit(true); Web−Anwendungen mit Java 442 Transaktionen /* * <p>Lastschrift</p> */ private static void fromAccount (Connection c, int account, int amount) throws SQLException { /* Alten Kontostand holen. */ PreparedStatement s; s = c.prepareStatement ("SELECT saldo FROM konten WHERE konto = ?"); s.setInt(1, account); ResultSet rs = s.executeQuery(); rs.next(); int balance = rs.getInt(1); /* Neuen Kontostand berechnen. */ balance −= amount; Web−Anwendungen mit Java 443 Transaktionen } /* Neuen Kontostand in Datenbank speichern. */ s = c.prepareStatement ("UPDATE konten SET saldo = ? " + "WHERE konto = ?"); s.setInt(1, balance); s.setInt(2, account); s.executeUpdate(); s.close(); /* * <p>Gutschrift</p> */ private static void toAccount (Connection c, int account, int amount) throws SQLException { fromAccount(c, account, −amount); } Web−Anwendungen mit Java 444 Transaktionen void setAutoCommit(boolean autoCommit) true: Jede SQL−Anweisung ist separate Transaktion. false: SQL−Anweisungen sind zu Transaktionen gruppiert. boolean getAutoCommit() void commit() Zustand erfragen Transaktion abschließen: Änderungen endgültig in Datenbank schreiben void rollback() Transaktion zurücksetzen: Änderungen verwerfen Web−Anwendungen mit Java 445 Transaktionen voreinander schützen void setTransactionIsolation(int level) Legt fest, wie stark Transaktionen voneinander abgeschottet sind. Sind Änderungen während einer Transaktion T1 bereits in einer Transaktion T2 sichtbar? Daumenregel: geringe Abschottung = hoher Durchsatz int getTransactionIsolation() Aktuellen Wert abfragen Web−Anwendungen mit Java 446 Transaktionen voreinander schützen Schmutzlesen (»dirty read«): Einmallesen (»non−repeatable read«) T1 schreibt Satz, T2 liest Satz, T1 bricht ab ⇒ Satz ist »dirty«. T1 liest Satz, T2 ändert Satz, T1 liest Satz noch einmal ⇒ Satz ist bei jedem Lesen anders. Phantomlesen (»phantom read«) T1 selektiert Relation mit Selektionsbedingung B, T2 fügt Satz ein, der B erfüllt, T1 selektiert Relation mit B noch einmal ⇒ Relation enthält zusätzlichen »Phantomsatz«. Web−Anwendungen mit Java 447 Transaktionen voreinander schützen TRANSACTION_READ_UNCOMMITTED Erlaubt Schmutzlesen, Einmallesen und Phantomlesen TRANSACTION_READ_COMMITTED Verhindert Schmutzlesen Erlaubt Einmallesen und Phantomlesen TRANSACTION_REPEATABLE_READ Verhindert Schmutzlesen und Einmallesen Erlaubt Phantomlesen TRANSACTION_SERIALIZABLE Verhindert Schmutzlesen, Einmallesen und Phantomlesen Web−Anwendungen mit Java 448 Parallele SQL−Anweisungen Datenbankoperationen benötigen viel Zeit 1 ... datenbankunabhängige Aktivitäten erledigen In separatem Thread ausführen und in der Wartezeit... Benutzeroberfläche aktualisieren usw. ... weitere Datenbankoperation parallel ausführen C C C S S S RS RS RS RS Web−Anwendungen mit Java 449 C C S S S RS RS RS Parallele Anweisungen: Varianten Paralleles ResultSet erzeugen Statement für weitere SQL−Anweisung nutzen Nicht möglich, da Statement.execute() das geöffnete ResultSet schließt Paralleles Statement erzeugen Connection für weiteres Statement nutzen JDBC−Treiber synchronisiert, falls nötig Güte der Synchronisierung abhängig von DBMS und Treiber Parallele Connection erzeugen