Java Database Connectivity API / JDBC Motivation Design Treiber Grundlagen Metadaten Transaktionen Exceptions Pratikum SWE 2 M. Löberbauer, T. Kotzmann, H. Prähofer 1
Motivation Problem: Zugriff auf DBMS nicht einheitlich Anwendung MySQL API MySQL Anwendung DB2 API DB2 Anwendung Oracle API Oracle 2
Motivation Lösung: Zwischenschicht MySQL API MySQL Anwendung JDBC API J D B C DB2 API DB2 Oracle API Oracle 3
Design Entwicklung seit: 1995 Erster Ansatz: Java erweitern Zweiter Ansatz: Treiber der Datenbankhersteller Vorbild: ODBC Unterschiede:! ODBC wenige Befehle, viele Optionen! JDBC viele einfache Methoden! ODBC nutzt void-zeiger! Java kennt keine Zeiger Flexibilität: JDBC erlaubt beliebige Zeichenfolgen! Anpassung an Datenbank möglich. Optimierung! Bindung " 4
Design Java Anwendung JDBC-Treibermanger JDBC/ODBC- Brücke JDBC-Treiber ODBC-Treiber Datenbank Datenbank 5
Treiber, Typ 1 und 2 Typ 1: Brücke! zb: JDBC/ODBC ODBC ist verbreitet! Testen, Experimentieren Windows Plattformen! Leistung " JDBC- Anwendung B r ü c k e ODBC ODBC- Treiber! Wartung "! Wenn kein JDBC-Treiber verfügbar ist Client DB Server Typ 2: Partial Java Driver! Gibt Aufruf an native API weiter! DB und OS abhängig " JDBC- Anwendung A P I DB! Nutzer braucht plattformabhängige API " Client Server 6
Treiber, Typ 3 und 4 Typ 3: Reiner Java Treiber zu Middleware! Portabel!! DB unabhängig!! Flexibel, mehrere DB möglich! JDBC- Anwendung M W DB! DB abhängiger Teil in Middleware Typ 4: Reiner Java Treiber zur DB! Portabel!! Schnell!! Verbindung Netz File I/O Embedded! Client braucht DB abhängige Treiber " Client JDBC- Anwendung Client Server Server DB Server 7
Treiber, Installation Download! http://developers.sun.com/product/jdbc/drivers! Datenbankhersteller Installation! Eintragen in den Klassenpfad Laden eines JDBC Treibers! System Property: jdbc.drivers java -Djdbc.drivers=org.apache.derby.jdbc.EmbeddedDriver Xyz System.setProperty("jdbc.drivers", "org.apache.derby.jdbc.embeddeddriver");! Manuelles laden der Treiberklasse Class.forName("org.apache.derby.jdbc.EmbeddedDriver");! Automatisch durch DriverManager Java Service: java.sql.driver 8
Verbindungsaufbau DriverManager! Verwaltet registrierte Treiber, baut Verbindungen auf Connection getconnection(string url, String user, String password) Datenbank URL! Aufbau: jdbc:<subprotrokoll>:<subname> jdbc:<datenbanktreiber>:<treiberspezifische Angaben>! Derby jdbc:derby:/path/to/database jdbc:derby:database! MySQL jdbc:mysql://<host>:<port>/<database>!"#$%&'()*+,-) 9
Verbindung Connection: Verwaltet Verbindung (Session) zur DB! close() Schließen der Verbindung! commit() Bestätigen vorangegangener Änderungen, Standard ist auto-commit! Statement createstatement() Statement stat = con.createstatement(); stat.executeupdate("insert INTO test VALUES ('Hallo')");! PreparedStatement preparestatement(string sql) PreparedStatement stat = con.preparestatement("insert INTO test VALUES ('Hallo')");... stat.executeupdate(); 10
Statement ResultSet executequery(string sql)! Ausführen einer SQL-Abfrage (SELECT) int executeupdate(string sql)! Ausführen eines Updates (UPDATE, INSERT, DELETE)! Liefert die Anzahl der betroffenen Zeilen boolean execute(string sql)! Ausführen beliebiger SQL-Anweisungen! Liefert true wenn eine Ergebnismenge geliefert wurde int getupdatecount()! Anzahl der betroffenen Zeilen, oder -1 wenn Anweisung keinen Zähler hatte ResultSet getresultset()! Ergebnismenge der letzten Abfrage, oder null wenn keine Ergebnismenge geliefert wurde void close()! Gibt alle JDBC Ressourcen frei Parameterlos! SQL-Injection 11
PreparedStatement void set<typ>(int n, <Typ> x)! Setzen des Parameters an der Stelle n (1..m) void clearparameters()! Löschen aller Parameterwerte ResultSet executequery()! Ausführen der vorübersetzten SQL-Anfrage (SELECT) int executeupdate()! Ausführen des vorübersetzten Updates (UPDATE, INSERT, DELETE)! Gibt die Anzahl betroffener Zeilen zurück 90% Lösung PreparedStatement stat; stat = con.preparestatement("insert INTO test VALUES (?,?)"); stat.setstring(1, "Hallo"); stat.setstring(2, "Welt"); stat.executeupdate(); stat.setstring(2, "Jane"); stat.executeupdate(); 12
CallableStatement Ausführen von DB-Prozeduren (SQL stored procedures) Parameterlose Prozedur! {call procedure_name } Prozedur! {call procedure_name(?,?,...)} Funktion! {? = call procedure_name(?,?,...) } Eingangsparameter analog zu PreparedStatement Ausgabeparameter müssen registriert werden! void registeroutparameter(int index, int sqltype) CallableStatement cs = con.preparecall("{ CALL GET_NUMBER_FOR_NAME(?,?) }"); cs.registeroutparameter(2, java.sql.types.integer); cs.setstring(1, "Duke"); cs.execute(); int number = cs.getint(2); 13
Batch Statements Kommandos Anfügen! Statements: void addbatch(string sqlcmd)! PreparedStatements: void addbatch() Löschen der Kommandos: void clearbatch() Ausführen der Kommandos: int[] executebatch() Statement stmt = conn.createstatement(); stmt.addbatch("insert into Person values(5, 'Herman', 'Hollerith', 1983)"); stmt.addbatch("insert into Person values(6, 'Larry', 'Ellison', 1977)"); int[] upds = stmt.executebatch(); PreparedStatement pstmt = conn.preparestatement("insert into Person values(?,?,?,?)"); for (Person p : persons) { pstmt.setint(1, p.getid()); pstmt.setstring(2, p.getfirstname()); pstmt.setstring(3, p.getlastname()); pstmt.setint(4, p.getyear()); pstmt.addbatch(); } int[] res = pstmt.executebatch(); 14
ResultSet ResultSet ist die Ergebnistabelle einer Abfrage Arten! einfache: können nur sequentiell von vorne nach hinten durchlaufen werden! scrollbare: erlaubt beide Richtungen und Positionierung! änderbare: erlauben Änderungen in der Datenbank! sensitive: zeigen Änderungen in Datenbank an Art beim Erzeugen des Statements festlegen: ResultSet.TYPE_FORWARD_ONLY ResultSet.TYPE_SCROLL_INSENSITIVE ResultSet.TYPE_SCROLL_SENSITIVE Statement preparestatement(string sql, int resultsettype, int resultsetconcurrency, int resultsetholdability) throws SQLException ResultSet.CONCUR_READONLY ResultSet.CONCUR_UPDATABLE ResultSet.HOLD_CURSORS_OVER_COMMIT ResultSet.CLOSE_CURSORS_AT_COMMIT 15
ResultSet Lesen Zeilenweise abarbeiten der Ergebnistabelle! boolean next() Anspringen der nächsten Zeile true solange gültige Zeile erreicht wird ResultSet beginnt vor der ersten Zeile! <Typ> get<typ>(int spalte)! <Typ> get<typ>(string spaltenname)! boolean wasnull() true wenn letzter Wert SQL-NULL war! int findcolumn(string spaltenname)!"#!$%&# '(&# getint(3) => 25 wasnull() => false getstring("name") => Max findcolumn("nr") => 1 next() *).$/),+),) 0%1'),-) 222) 222) 222) 16
ResultSet Navigation boolean first()! Erste Zeile im ResultSet! true wenn gültige Zeile erreicht void beforefirst()! Vor die erste Zeile im ResultSet boolean last()! Letzte Zeile im ResultSet! true wenn gültige Zeile erreicht void afterlast()! Nach letzter Zeile im ResultSet boolean absolute(int row)! Eine Zeile anspringen row > 0... von oben (1 erste Zeile, 2 zweite Zeile,...) row < 0... von unten (-1 letzte Zeile, -2 vorletzte Zeile,...)! true wenn gültige Zeile erreicht int getrow()! Nummer der aktuellen Zeile 17
ResultSet Update void update<typ>(int col, <Typ> x)! ändert die aktuelle Zeile void updaterow()! schreibt die aktuelle Zeile in die Datenbank zurück void movetoinsertrow()! bewegt den Cursor auf die "insert row" void insertrow()! der Cursor muss vorher mit movetoinsertrow() auf die "insert row" bewegt werden.! fügt eine neue Zeile in die Datenbank ein void deleterow()! löscht aktuelle Zeile aus der Datenbank 18
Typen SQL-Typ CHAR, VARCHAR, LONGVARCHAR NUMERIC, DECIMAL BIT TINYINT SMALLINT INTEGER BIGINT REAL FLOAT, DOUBLE Java-Typ String java.math.bigdecimal boolean byte short int long float double BINARY, VARBINARY, LONGVARBINARY byte[] DATE TIME TIMESTAMP java.sql.date java.sql.time java.sql.timestamp... siehe JSR-221, Appendix B, Date Type Conversion Tables 19
Metadaten, Datenbank Erstellen über Verbindung! DatabaseMetaData <Connection>.getMetaData() Allgemeines! Connection getconnection()! String geturl()! String getusername()! boolean isreadonly() Eigenschaften! boolean supportsansi92entrylevelsql()! boolean supportstransactions()! boolean supportsgroupby() Beschränkungen! int getmaxstatementlength()! int getmaxstatements()! int getmaxconnections() 0.. kein Limit oder unbekannt 20
Metadaten, Tabellen ResultSet gettables(string catalog, String schema, String table, String[] types) ResultSet getcolumns(string catalog, String schema, String table, String column)! catalog: Name des Katalogs "" Tabellen ohne Katalog, null Katalognamen nicht berücksichtigen! scheme: Schemaname "" Tabellen ohne Schema, null nicht berücksichtigen! table: Tabellenname null nicht berücksichtigen! column: Spaltenname null nicht berücksichtigen! types: zb: "TABLE", "VIEW", "SYSTEM TABLE", "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM" null nicht berücksichtigen 21
Metadaten, Ergebnismenge Erstellen! ResultSetMetaData <ResultSet>.getMetaData() Eigenschaften! int getcolumncount()! String getcolumnname(int!column)! int getcolumntype(int!column)! String getcolumntypename(int!column)! String gettablename(int!column)! boolean iscurrency(int column) getcolumncount() => 3 getcolumnname(1) => "Nr" getcolumntype(3) => 4 // INTEGER getcolumntypename(3) => "int" gettablename(2) => "users" Nr Name Title... Age 1 Max DI... 30 2 Susi Mag... 28............... SELECT Nr, Name, Age FROM users; Nr Name Age 1 Max 30 2 Susi 28......... 22
Transaktionen Standard: auto-commit, jedes Statement eine Transaktion! boolean <Connection>.getAutoCommit()! void <Connection>.setAutoCommit(boolean ac) Transaktion startet automatisch Transaktion abschießen! void <Connection>.commit() Transaktion rücksetzen (zb: SQLException)! void <Connection>.rollback() Auto-Commit Savepoints Connection con;... try { con.setautocommit(false); Statement stat = con.createstatement(); stat.executeupdate("insert..."); stat.executeupdate("insert..."); stat.executeupdate("update..."); con.commit(); } catch (SQLException e) { con.rollback(); } Transaction- Isolation NONE READ_UNCOMMITTED READ_COMMITTED REPEATABLE_READ SERIALIZABLE 23
Exceptions SQLException SQLNonTransient Exception SQLTransient Exception SQLRecoverable Exception Erneuter Versuch wird fehlschlagen Erneuter Versuch kann durchgehen Erneuter Versuch nach Eingriffen kann durchgehen 24
JavaDB, Derby Seit Java 6.0 mitgeliefert Web-Seite: http://db.apache.org/derby Umgebungsvariablen! JAVA_HOME=Pfad zur Java JDK Installation! DERBY_HOME=Pfad zur Derby Installation! PATH um DERBY_HOME/bin erweitern JARs in DERBY_HOME/lib! derby.jar, Engine, genügt für embedded DB! derbynet.jar, Netzzugriff, Serverseitig! derbyclient.jar, Netzzugriff, Clientseitig! derbytools.jar, Verwaltungs-Werkzeuge! derbyrun.jar verweist auf: derby.jar derbyclient.jar derbytools.jar derbynet.jar 25
JavaDB, Derby Installation & Treiber DERBY_HOME (Windows)! C:\Programme\Sun\JavaDB Derby Hilfsprogramme! DERBY_HOME\bin Derby Driver! org.apache.derby.jdbc.clientdriver! org.apache.derby.jdbc.embeddeddriver Derby Connection-String! jdbc:derby://localhost:1527/name_of_db! jdbc:derby:name_of_db Derby Network- Server muss gestartet sein! Embedded, Zugriff auf Dateien: Nur eine Verbindung möglich! 26
JavaDB, Derby, Kommandozeilen-Werkzeuge Systeminformation: sysinfo 27
JavaDB, Derby, Kommandozeilen-Werkzeuge Kommandozeilenwerkzeug: ij Verbinden zu (und evtl. erzeugen einer) Datenbank Tabelle erzeugen Beschreibung einer Tabelle 28
JavaDB, Derby, Kommandozeilen-Werkzeuge Kommandozeilenwerkzeug: ij Einfügen eines Datensatzes Aktualisieren eines Datensatzes Löschen einer Tabelle 29
Zusammenfassung Datenbankunabhängigkeit! Zwischenschicht -> einfachere Programmentwicklung! Treiberschnittstelle (min. SQL 92 entry level) 4 Treiberarten! Brücke (zb: JDBC -> ODBC)! Teilweise Java! Java zu Middleware! Java zu Datenbank Beliebige SQL-Kommandos absetzbar! Optimierung vs. Datenbankunabhängigkeit Statements! PreparedStatement Vorkompiliert, mit Parametern (Sicher gegen SQL-Injection)! Statement Statisch, Abfragen vom Benutzer, keine Parameter! CallableStatement Stored Procedures, mit IN, OUT und INOUT Parametern 30