2 Datenbankanwendungen mit JDBC JDEe (Iava Database Connectivity) ist die Standard-Schnittstelle für den Zugriff auf relationale Datenbanken mittels SQL. Der Kern von JDBC besteht aus einer Sammlung von Klassen und Interfaces, die im Paket java. sql zusammengefasst sind. Damit können Datenbankverbindungen aufgebaut, beliebige SQLAnweisungen an die Datenbank geschickt und Abfrageergebnisse im Programm verarbeitet werden. Java-Programme, die mittels ]DBC auf eine Datenbank zugreifen, enthalten keinen datenbankspezifischen Code, Das ermöglicht den Austausch des Datenbanksystems (DBMS), ohne das Programm ändern zu müssen. JDBC stellt also eine datenbankneutrale Zugriffsschnittstelle bereit JDBC ist eine Abstraktionsschicht zwischen Java-Programm und SQL sua z.i Java-Programm JDBC SOL relationales DBMS Die zur Zeit der Drucklegung dieses Buches aktuelle Version JDBC 4,0 ist Teil von Java SE 6, In diesem Kapitel beschäftigen wir uns mit den Grundlagen von JDBc' 2.1 Die Architektur von JDBC-Anwendungen Im einfachsten Fall wird JDBC im Client-Programm verwendet, das lokal oder über ein Netzwerk auf eine Datenbank zugreift (zweistufige Architektur), Eine dreistufige Architektur trennt in der Regel die Anwendungslogik von der Benutzungsoberfläche und der Datenverwaltung, Ein Client (z. B, Webbrowser) kommuniziert über ein geeignetes Protokoll (z. B, HTTP) mit dem Web-/Applikations- Schichtenmodell einer Datenbankanwendung 2 Datenbankanwendungen mit JDBC 28 server, der seinerseits auf den Datenbankserver zugreift. Bild 2.2 zeigt, wie verteilte Systeme mit JDBC realisiert werden können. Bild 22.· 2- und 3-stufige Architekturen Client Client Java-Anwendung Java-Anwendung oder Java-Applet JDBC z.s. HTTP Server Java-Anwendung datenbankspezifisches Protokoll JDBC Server Serve r DBMS Treiberkonzept datenbankspezifisches Protokoll DBMS Jedes Datenbanksystem hat eine eigene, herstellerspezifische Zugriffsschnittstelle. Ein so genannter IDEe-Treiber übersetzt die JDBC-Methodenaufrufe in Aufrufe dieser Schnittstelle. So wird für jedes DBMS ein eigener Treiber benötigt. JDBC-Treiber existieren für die meisten relationalen DBMS. Beim JDBe-Treiber zu einem DBMS handelt es sich um eine Bibliothek von Klassen, die die von JDBC vorgegebenen Interfaces implementieren. Bild 23· }DBC-Treiber Java-Anwendung JDBC JDBC-Treiber für Oracle Oracle 11 JDBC-Treiber für DB2 DB2 11 JDBC-Treiber für MySQL MySQL 2.1 Die Architektur von JDBC-Anwendungen 29 Zur Realisierung von ]DBC-Treibem gibt es grundsätzlich vier Möglichkeiten. die im Bild 2.4 zusammengefasst sind. Bild 2.4.' Typen von ]DBCTreibern Client Typ 1 JCOC-API JavaAnwen ÖJng JCOC-OCOCBridge Datenbank OB·Server EJ Tre ber C1ient- DBIvIS-al1längiges Prct okd l Naive- AR· Treiber Oient- DBIvIS-al1längiges Prctokd l JavaAnwen ÖJng DBM S· DBMS-un oohängiges Protokoll ~ al1loogges ~ Protokoll ~ Middleware Typ' JavaAnwenctlng ~ DBM S Bibliothek Typ 3 JCOCNetTreiber ~ DBMS Bibli cthek Typ 2 JavaAnwen ÖJng ~ DBM S ~ NaivePrct okol ~ DBMS- oohängiges Protokoll Treiber ]DBC-ODBC-Bridge und ODBC-Treiber Typ1 Treiber dieses Typs benutzen das ODBC-API (Open Database Connectivity) von Microsoft, um auf relationale Datenbanken zuzugreifen. Ein ]DBC-ODBC-Brückentreiber wandelt alle JDBCAufrufe in ODBC-Aufrufe um. Somit lassen sich vorhandene ODBC-Treiber nutzen. um Datenbankanwendungen mit JDBC zu realisieren. Allerdings setzt das eine ODBC-Installation auf dem Client voraus. Die JDBC-ODBC-Bridge ist als Paket sun. jdbc. odbc Bestandteil von Java SE. Native-API-Treiber Typ2 Ein solcher Treiber nutzt die DBMS-spezifische Programmierschnittstelle. Für jeden Client müssen c!ientseitige Bibliotheken (Binärcode) geladen werden. ]DBC-Net-Treiber Typ3 Treiber dieses Typs sind vollständig in Java realisiert. Sie nutzen eine zusätzliche Komponente (Middleware), die das DBMSunabhängige Protokoll in ein DBMS-spezifisches Protokoll über- ~ DBM S 2 Datenbankanwendungen mit JDBC 30 setzt. Diese Lösung ist sehr flexibel, da von einem Wechsel des DBMS nur die Middleware betroffen ist. Typ 4 Native-Protokoll-Treiber Diese vollständig in Java implementierten Treiber übersetzen JDBC-Aufrufe direkt in das DBMS-spezifische Protokoll. Sie werden meist von den DBMS-Herstellem selbst angeboten. Bild 2.5 gibt einen überblick über die wichtigsten Klassen und Interfaces des JDBC-APl. Bild2S· javasql Wichtige Klassen und Interfaces von java.sql Klassen Types Treiberklassen Interfaces sun.jdbc.odbc <cinterface»> Driver ------ --1 JdbcOcbcD"iver <cinterface»> Connection ------ --1 JcbcOcbcConnection ------ --1 JdxOdbcSta:emenl jeva.utilDete - ----- -- <cinterface»> ResultSet java lang Excernon <cinterface»> DatooaseMetEOata <cinterface»> ResultSetMetaData ----{> erenos ------{> JcbcOcbcPreparedStaement JdbcOdbcResultSet - ----- -- .u tc o ctc t etete seweteöa a - ----- -- JclxO cbcResultSetMetaData implements Die Klasse DriverManager handhabt das Laden des JDBC-Treibers und bietet Methoden zum Aufbau einer Datenbankverbindung. Die wichtigsten Interfaces: • Connection repräsentiert eine Datenbankverbindung. • Statement wird verwendet, um SQL-Anweisungen über eine gegebene Datenbankverbindung auszuführen. • Resu ltSet bietet Methoden, um auf das Ergebnis einer SQLAbfrage zuzugreifen. 2.2 Erste Beispiele 31 Bild 2.5 zeigt auch die Klassen der ]DBC-ODBC-Bridge, die die Interfaces implementieren. 2.2 Erste Beispiele 2.2.1 Die Beispiel-Datenbank Um verschiedene Aspekte von JDBC in den nächsten Abschnitten demonstrieren zu können, benutzen wir eine Datenbank, die Bücher verschiedener Verlage verwaltet. Bild 2.6 zeigt die Beziehung zwischen den Entitätstypen verlag und buch. Bild 2 .6 , verl ag 11 n ----- Datenmodell buch I ----- Ein Verlag hat verschiedene Bücher veröffentlicht. Ein Buch ist in genau einem Verlag erschienen. In einer relationalen Datenbank werden die Entitätstypen als Tabellen aus Zeilen und Spalten implementiert. Die Spalten repräsentieren die verschiedenen Merkmale einer Entität, beim Buch also z. B. ISBN-Nurrnner, Autor, Titel usw . Jede Zeile enthält alle Angaben zu einer einzigen Entität. 1 isbn I ~" 1, 01 I ousQobe 3-15-001308- 9 Oickens, Ch"Jles Schwere Zeiten K" rtoniert 3-15-00156 2-6 Dicke ns, Ch"Jles Große Erw" rtungen K" rtoniert s", on, oI> ,m '", ,e 1989 ~ 1993 s 3-15-010606- 0 Dick ens, Ch"rles Der We ihn" chts" bend Gebunden ua 2006 3-257-20998-3 Dickens, Ch"rles Nikol" s Nickleby K" rtoniert ;ce 1997 3-257-21034-5 Dickens, Ch"rles D"vid Copp erlield K" rtoniert 3-257-21166 -1( Dicke ns, Ch"rles Ble"kh" us K" rtoniert 3-257-21405-7 Dick ens, Ch"rles Die Pickwickier K" rtoniert ece e" es 3-257-21406-5 Dickens, Ch"rles M"rtin Chu" lewit K" rtoniert en 3-351-030 44-4 Dickens, Ch"rles W eihn" chten mit Dickens Gebunden 3-458-3 2655-3 Dicke ns, Ch"rles H" rte Zeiten K" rtoniert 3-458-32733- 9 Dick ens, Ch"rles Geschichte " us , wei Stiidten 3-458-32810-6 Dickens, Ch"rles Ble"k House 3-458-3300 4-6 Dickens, Ch"rles 3-491-9600 7-1( Dickens, Ch"Jles ,C ' 1982 1984 2002 1998 , , , , , ~ 2005 .. e_' Bild 2.7: n Ausschnitt aus der 12,6 C~ 14,9 14,9 14,9 12,9 13.9 in 200 4 C 11,5 K" rtoniert m ,e; 1987 C 12,5 K" rtoniert 1 030 1988 C 1 011 1991 Nikol" us Nickleby K" rtoniert W eihn" chtserziihlungen Gebunden ,~, 2000 3-538-0 5349-9 Dickens, Ch"rles D"vid Copperliel d Leinen 1 023 1955 3-538-06656-6 Dickens, Charles Die Pickwickier Gebunden 1 039 1997 3-538-0665 7-4 Dickens, Ch"rles M" rtin Chu" lewit Gebunden 1 000 1997 3-538-066 58-2 Dickens, Ch"rles Nichol" s Nickleby Gebunden ~% 1997 3-538-06982- 4 Dickens, Ch"rles Nichol" s Nickleby Gebunden ~% 2004 D" s Geheimnis des Edwin Drood Leinen 3-7175-197 6-1( Dickens, Charles verlOOL" oe, 2001 C , , , , , a r " " 9,95 44,9 12,9 12,9 12,9 24,9 24,9 Tabelle buch 2 Datenbankanwendungen mit JDBC 32 Primärschlüssel Jede Tabelle enthält einen Prirnärschlüssel, der durch eine Spalte oder eine Kombination von mehreren Spalten repräsentiert wird. Der Wert des Primärschlüssels identifiziert höchstens eine Zeile der Tabelle, Die Schlüsselwerte einer Tabelle sind alle voneinander verschieden, Für die Tabelle buch bietet sich die Spalte isbn als Primärschlüssel an. Fremdschlüssel Um Verknüpfungen zwischen Tabellen herstellen zu können, werden so genannte Fremdschlüssel benötigt. Ein Frerndschlüssel einer Tabelle wird durch eine Spalte oder durch eine Kombination von mehreren Spalten repräsentiert. Ein Fremdschlüssel ist in einer anderen Tabelle Primärschlüssel. Durch Vergleich von Fremdschlüsselwert und Primärschlüsselwert werden Beziehungen zwischen Zeilen der beiden Tabellen hergestellt So hat die Tabelle buch den Primärschlüssel isbn, die Tabelle verlag den Primärschlüssel ierlag id. Die Tabelle buch besitzt den Fremdschlüssel uerlagid (siehe Tabellenstruktur weiter unten), Fremdschlüsselwerte innerhalb einer Tabelle sind in der Regel natürlich nicht eindeutig. Die l:n-Beziehung zwischen verlag und buch wird also durch die Fremdschlüssel-Primärschlüssel-Beziehung etabliert Im Weiteren setzen wir Grundkenntnisse der Datenbanksprache SQL voraus, Die SQL-Anweisungen zur Erstellung der zwei Tabellen für das Datenbanksystem MySQL sind, Tabelle verlag create table verl ag ( ver l a9_l d lnteger, verlag_na me varchar (30) , webadress e varchar (30) , primary key (ver lag i d) Tabelle buch create tabl e buch ( isbn varchar (17) , autor varchar(30), titel varchar(SO), ausgabe varchar(20), sei t enzahl l nte ger , jahr lnteger, ver l ag_l d lnteger, preis double, best and lnteger, stand dat etlme, primary key (isbn), for eign key (ver lag_i d) references verlag (verlag_i d) 2.2 Erste Beispiele 33 Die Programme dieses Kapitels "WUrden mit verschiedenen Datenbanksystemen getestet. Es "WUrden solche Systeme bevorzugt, die weit verbreitet bzw. ohne allzu großen Aufwand installiert werden können. Die folgende Tabelle führt die Datenbanksysteme und eingesetzten ]DBC-Treiber auf. Bezugsquellen können am Ende des Buches nachgeschlagen werden. DBMS JDBC-Treiber Microsoft Access ]DBC-ODBC-Bridge 1 MySQL Community Server 5.1.43 MySQL Connector/] 5.1.11 4 Apache Derby 10.5.3.0 Apache Derby Network Client ]DBC Driver 10.5.3.0 Typ Eingesetzte DB-Produkte undJDBC-Treiber 4 Access. MySQL (mit Tabellentyp InnoDB) und Apache Derby unterstützen Transaktionen (siehe Kapitel 2.4.1) und stellen die referentielle Integrität sicher. 2.2.2 Verbindungsaufbau Alle Programme sind so geschrieben, dass die Datenbanksysteme leicht gewechselt werden können. Dazu sind die datenbankspezifischen Angaben in einer Konfigurationsdatei dbconnect. properties ausgelagert, die mit dem Texteditor bearbeitet werden kann. In diesem Abschnitt zeigen wir den grundsätzlichen Aufbau einer ]DBC-Anwendung. Wir setzen voraus, dass die Datenbank bereits eingerichtet ist und Testdaten enthält. In den folgenden Abschnitten dieses Kapitels werden dann der Aufbau und das Laden von Datenbanken mittels ]DBC-Programmen ausführlich beschrieben. SQL-Skripte und Testdaten sowie Skripte zum Starten und Stop- Online-Seroice pen des Derby-Servers können der zum Download zur Verfügung gestellten Programmsammlung (Online-Service) entnommen werden. Das erste Programmbeispiel gibt Informationen über die benutz- Programm 21 te Datenbank und das eingesetzte DBMS aus. Datenbankspezifische Angaben (Treiber, Identifikation der Datenbank, User-Id und Passwort) befinden sich in der Datei dbconnect.properties. 2 Datenbankanwendungen mit ]DBC 34 # MS Access #driver~sun.jdbc.o dbc.JdbcQdbcDriver #url~jdbc:odbc buecher # MySQL drlver=com.mysql.jdbc.Drl ver ur1-tjdbc: mysq 1: 111 oca1hostibuecher user=root password=root # Apache De rby #dri ver~org.apache.derby.jdbc.Cl ientDriver #url~jdbc :d erby:ll localhost/buecher #user=admin #password=secret Hier sind die Angaben zur MySQL-Datenbank aktiviert. Das Zeichen # leitet einen Kommentar ein. Die Access-Datenbank muss mit dem Namen buecher als ODBC-Datenquelle registriert sein. Bei Windows muss zu diesem Zweck das Programm DatenquelZen (ODBC) aus der Systemsteuerung aufgerufen und der MSAccess-Treiber sowie die Access-Datenbank ausgewählt werden. DBMetaData import import import import ja va ja va ja va ja va io. File InputStream; sql .Connection; sq l . DatabaseMetaData; sq l . Dri verManager ; impor t ja va util.Properties; publ ic class DBMetaData ( public stat ic void main(String[] args) throws Except i on ( // OB-Parameter einlesen Fi lel nput St ream in ~ new Fi lelnput St ream( "dbconnect.prop erties"); Properties prop = new Properti es( ); prop.load(in) : in.clos e(): Stri ng Stri ng String String dri ver ~ prop. getProperty( "dri ver"): ur1 ~ prop.getProperty(" ur1"): user ~ prop.getProperty ("user" . ""): password ~ prop.getProperty("password". ""): 11 Treiber laden if (driver !~ null) Class.forName(dri ver): 11 Verb indung zur OB herstellen Connectlon con = Drl verManager.getConnectlon(url, user, 2.2 Erste Beispiele 35 password) ; DatabaseMetaData dbmd ~ con.getMetaData(); System.out. pri nt 1n(" URL; " + dbmd. getURl()); System.out. pri nt 1n(" UserNarre ; " + dbrrd. getUserNarre ()); System.out. pri nt 1n(" DatabaseProductName; " + dbrrd.getDatabaseProductName()); System.out. pr i nt 1n("OatabaseProductVerSl on: + dbrrd.getDatabaseProductVersion ()); System.out. pri nt 1n(" DriverNarre; + dbrrd getDri verNarre ()); System.out. pr i nt 1n(" DrlverVerSl on: + dbrrd.getDri verVersion()); con clos e(); Zunächst werden die Verbindungsparameter aus dbconnect. properties eingelesen und in entsprechenden Variablen abgelegt. Die Treiberklasse (sun. jdbc. odbc. JdbcOdbcDri ver für Access, }DBC-Treiber laden com.mysql.jdbc.Driver für MySQL und org.apache.derby.jdbc. Cl i entDri ver für Apache Derby) wird mit Class. forNarre ( ... ) explizit geladen. Hierbei wird ein statischer Initialisierungsblock der Treiberklasse ausgeführt. der dafür sorgt. dass eine neue Instanz der Treiberklasse beim Treibermanager (java. sq 1. Drlver Manager) registriert wird. Verwendet man die neuesten Treiberversionen, so kann auf das Autoloading explizite Laden ab Java SE 6 verzichtet werden. Dann kann auch die Eigenschaft driver in der Datei dbconnect.properties weggelassen werden. Sicherzustellen ist. dass die Klassen des JDBC-Treibers. zusammengefasst in einer Datei (z. B. mysql-connector-java-5. 1. 11bln.jar) vom Class-Loader gefunden werden. Dazu muss für die Programmausführung die entsprechende jar-Datei in den Klassenpfad aufgenommen werden. Die Programmsammlung des Online-Service enthält hierzu Vorschläge. Um eine Verbindung zur Datenbank aufbauen zu können, muss Verbindung zur diese Datenbank genau identifiziert werden. Die Zieldatenbank Datenbank wird in URL-Schreibweise angegeben, herstellen jdbc:<subprotokoll>:<subname> Hierbei bezeichnet das Subprotokoll die Art des verwendeten Treibers (z. B. odbc, mysql oder derby), Subname bezeichnet die eigentliche Datenbank. Bei Access ist das der Name der ODBCDatenquelle. bei MySQL und Apache Derby ein URL (Uniform Resource Locator): / /1 oca1hostibuecher. Die Datenbank liegt hier 36 2 Datenbankanwendungen mit ]DBC auf dem eigenen Rechner. Statt 1oca1hast kann auch der Name eines im Netz verbundenen anderen Rechners angegeben sein. Die Dokumentation zum Treiber enthält, was als Subprotokoll und Subname anzugeben ist. DriverManager Die Klasse java.sql.Drl verManager kann unterschiedliche Treiber zur selben Zeit handhaben. Wird eine Verbindung zu einer Datenbank angefordert, so versucht der Treibermanager, den passenden Treiber zu finden und zur Herstellung der Verbindung zu verwenden. Die Drl verManager-Methode getConnectlon stellt eine Verbindung zur Datenbank her. statlc Connectlon getConnection(Strlng url) throws SQLException static Connection getConnection(String url, Properties info) throws SQLException static Connection getConnection(String url, String user, String password) throws SQLException Alle Methoden liefern ein Connecti on-Objekt und erwarten einen URL und ggf, weitere Angaben als Parameter, Datenbank-User und Passwort können angegeben werden. Bei der zweiten Methode sind die zusätzlichen Verbindungsparameter user und password im Properties-Objekt info eingetragen. SQLException Fehler werden in ]DBC grundsätzlich als Ausnahmen der Klasse java. sq1.SQLExcepti on ausgelöst Connection Ein Objekt vom Schnittstellentyp java. sq1.Connecti on präsentiert eine Verbindung zur Datenbank. re- Ein SQL-Anweisungsobjekt wird über die Connecti on-Schnittstelle erzeugt: Statement createStatement() throws SQLException Eine aktive Verbindung zur Datenbank wird mit der Methode close geschlossen: voi d close() throws SQLException Der Zustand einer Verbindung kann abgefragt werden, boolean isClosed() throws SQLException DatabaseMetaData Das Interface DatabaseMetaData repräsentiert Informationen über die Datenbank und das DBMS, Die folgende ConnectiorcMethode liefert ein Metadaten-Objekt 2.2 Erste Beispiele 37 Database MetaData getMetaData() t hr ows SQLException Die Schnittstelle umfasst weit über 100 Methoden. Hier werden nur einige aufgeführt, die auch im nächsten Beispiel verwendet werden. String getURL() throws SQL Exception liefert den URL der Datenbank. String getUserName() throws SQLException liefert den Namen des Datenbank-Users. String getDatabaseProductName() throws SQLException liefert den Produktnamen des Datenbanksystems. Str ing getDatabaseProductVersion() throws SQLException liefert die Version des Datenbanksystems. String getDriverName() throws SQLException liefert den Namen des ]DBC-Treibers. String getDriverVersion() throws SQL Exception liefert die Version des ]DBC-Treibers. mkd i r bi n javac -d bin src/DBMetaData.java java -cp bin %JDßC DR IV ER% DEMetaData Die Umgebungsvariable JDßC_DR IVER referenziert die jar-Datei des ]DBC-Treibers. Ausgabe, URL: jdbc: mysq 1: / /1 oca1hostlbuecher UserName: root@localhost DatabaseProductName: MySQL DatabaseProductVers ion: 5. 1. 43-community DriverName : MySQL-AB JDBC Driver Dri verVerslon: mysql-connector-java-5. 1. 11 ( Revision: l{s vn.Revision) ) 2.2.3 Datenbankabfragen mit SELECT Das Grundgerüst einer typischen ]DBC-Anwendung besteht aus den folgenden Schritten, 1. ]DBC-Treiber laden (entfällt bei Autoloading) 2. Verbindung zur Datenbank herstellen 3. SQL-Anweisungsobjekt erzeugen 4. SQL-Anweisung ausführen 5. Ergebnisse der SQL-Anweisung verarbeiten 6. Ressourcen freigeben Test 2 Datenbankanwendungen mit ]DBC 38 Programm 2.2 Das zweite Programmbeispiel zeigt Informationen zu Büchern in Form einer sortierten Liste an. Die hierzu nötige SQL-Anweisung lautet select autor, t i t el . isbn from buch order by autor, titel Buecherlistel import ja va io.File InputStream; import ja va sql .Connection; import ja va sq l . Dri verManager; import java sql.ResultSet; import java sql.Staterrent; import java util.Properties; public class Buecherlistel ( public static void main(String [] args) throws Exception ( // OB-Parameter einlesen FilelnputStream in ~ new FilelnputStream( "dbconnect.properties"); Properties prop = new Properties(); prop.load(in) ; in.close(); Stri ng ur 1 ~ prop. getProperty(" ur 1"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); /I Autoloading // Verbindung zur OB herstellen Connection con = DriverManager.getConnection(url, user, password) ; // SQL-Abfrage ausführen Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("select autor, t i t el . " + "i sbn from buch order by autor. ti te 1"); // Ergebnisse ausgeben whi 1e (rs. next ()) ( System.out.println(rs.getS tring(l)); System.out.println(rs.getString(2)); System.out.println("ISBN " + rs.getString(3)); System.out.println(); rs.close(); stmt.close(); con.close() ; 2.2 Erste Beispiele Das Interface java.sql.Statement ist die Basisschnittstelle für alle Statement SQL-Anweisungsformen. Mit der Staterrent-Methode executeQuery wird eine SELECTAnweisung ausgeführt: ResultSet executeQuery(String sql) throws SQLException Die Methode liefert ein ResultSet-Objekt mit dem Abfrageergebnis. Die SQL-Anweisung sq1 enthält kein Abschlusszeichen wie z.B. ";". Solche Abschlusszeichen können von DBMS zu DBMS variieren und werden demzufolge vom Treiber gesetzt. void close() throws SQLException gibt die mit der Ausführung gebundenen Ressourcen frei. Hierdurch wird auch das evtl. vorhandene ResultSet-Objekt geschlossen. Das Ergebnis einer SQL-Abfrage (eine Relation mit Zeilen und ResultSet Spalten) wird durch ein Objekt mit der Schnittstelle Resu ltSet repräsentiert. Zum Navigieren über die Zeilen der Ergebnismenge wird die Resu ltSet-Methode next genutzt boolean next() t hr ows SQLException Ein interner Cursor zeigt auf die aktuelle Ergebniszeile. Zu Beginn ist der Cursor vor der ersten Zeile positioniert. next liefert so lange true, wie das Weitersetzen des Cursors erfolgreich war. 1st die Ergebnismenge leer oder das Tabellenende erreicht und keine weitere Zeile mehr vorhanden, so wird fa l se zurückgeliefert. void close() t hr ows SQLException gibt die genutzten Ressourcen frei. Nachdem der interne Cursor auf eine Ergebniszeile positioniert SpaltenzugriJJ "WUrde, kann auf die Spaltenwerte dieser Zeile von links nach rechts zugegriffen werden. Für den Spaltenzugriff existieren verschiedene ResultSetMethoden getXxx. Es kann über den Spaltenindex. der bei 1 beginnt. oder über den Spaltennamen zugegriffen werden. Zu beachten ist, dass der Spaltenindex sich auf die Spaltennummer in der Ergebnismenge und nicht in der Originaltabelle bezieht. getStri ng liefert den Spaltenwert als ein Stri ng-Objekt Str ing getString(int column lndex) t hrows SQL Exception String getString(String columnName) throws SQL Exception 39 2 Datenbankanwendungen mit ]DBC 40 Programmausgabe: Dl ckens, Char les 81eak House IS8N 3-458-32810-6 Ol ckens, Charles 81 eakhaus IS8N 3-257-21166-X Dlckens, Charles Das GehelmnlS des Edwl n Drood IS8N 3-7175-1976-X Programm 23 Im Unterschied zu Programm 2.2 wird im folgenden Programm auch der Verlag eines Buches ausgegeben: Dlckens, Charles Bl eak House IS8N 3-458-32810 6 Insel Die SQL-Anweisung enthält einen so genannten join zwischen den Tabellen buch und verlag. Buecherliste2 (Ausschnitt) ResultSet rs ~ stmt .executeQuery("select autor, tt tel . r sbn. verlag narre + "fram buch inner join verlag" + "on buch. verla9_l d = verlag.v erlag~ld + "order by autor. ti te 1"); whi 1e (rs. next ()) ( System.out.pr i ntln(rs.getString( I)); System.out.pr i ntln(rs.getString(2)); System.out.println(" IS8N " + rS.g etString(3) + rS.g etString(4)); System.out.println(); Programm 2.4 + " " Mit dem folgenden Programm kann gezielt nach einem Titel gesucht werden. Dazu muss als Kommandozeilenparameter der Titel oder nur ein Teil des Titels beim Aufruf angegeben werden. 2.3 JDBC-Datentypen ResultSet rs ~ 41 stmt Buecherliste3 (Ausschnitt) .executeQuery("select autor, t i t el , isbn, verlag name + + + "fram buch inner join verlag" "on buch.verla9_id = verlag.verla9_id "where titel like '%" + titel + "%' order by autor, titel"); 2.3 JDBC-Datentypen Bei den SQL-Datentypen, die von den verschiedenen Datenbanksystemen unterstützt werden, gibt es zum Teil erhebliche Unterschiede. SQL arbeitet mit anderen Datentypen als Java. Beim Zugriff auf die Spaltenwerte und bei der Belegung von Parametern in JDBCProgrammen (siehe PreparedStatement im nächsten Abschnitt) muss daher eine Abbildung zwischen beiden Typsystemen erfolgen. Um die am meisten verwendeten SQL-Datentypen einheitlich zu ]DBC-Datentypen repräsentieren, "WUrden so genannte JDBC-Datentypen definiert, die in der Klasse java.sql.Types als Konstanten vom Typ int zusammengefasst sind: public static final int XXX Bild 2.8 gibt eine Übersicht über die ]DBC-Datentypen und ihre Entsprechungen für die Datenbanksysteme Access, MySQL und Apache Derby. Bild 2,8.' Abbildung zwischen ]DBC-Typen und SQL-Typen SQL-Typ Access SQL-Typ MySQL TINYINT BYTE TINYINT SMALLINT SMALLINT SMALLINT SMALLINT INTEGER INTEGER, COUNTER INTEGER, INTEGER JDBC-Typ SQL- Typ Derby Integerzahlen MEDIUMINT BIGINT BIGINT BIGINT REAL FLOAT REAL DOUBLE, FLOAT DOUBLE, REAL DOUBLE, FLOAT Gleitkommazahlen REAL FLOAT DOUBLE 2 Datenbankanwendungen mit JDBC 42 SQL-Typ Access JDBC-Typ SQL- Typ MySQL SQL-Typ Derby DECIMAL(p,s), DECIMAL(p,s) NUMERIC(p,s) NUMERIC(p,s) DATE DATE, YEAR DATE TIME TIME TIME DATE, TIME, TIMESTAMP, TIMESTAMP DATETIME DATETIME CHAR CHAR(n) CHAR(n) CHAR(n) VARCHAR VARCHAR(n), TEXT VARCHAR(n) VARCHAR(n) LONGVARCHAR LONGCHAR, TINYTEXT, TEXT, LONG VARCHAR LONGTEXT MEDIUMTEXT, Festkommazah len DECIMAL NUMERIC CURRENCY Datum und Uhrzeit TIMESTAMP Zeichenketten LONGTEXT Binärdaten BIT BIT BINARY BINARY(n) BOOLEAN CHAR(n) FOR BIT DATA VARBINARY VARBINARY(n) TINYBLOB VARCHAR(n) FOR BIT DATA LONGVARBINARY LONGBINARY BLOB, LONG VARCHAR MEDIUMBLOB, FOR BIT DATA LONGBLOB Beim Lesen der Werte aus einem Resu ltSet-Objekt bzw. bei der Übergabe von Parametern an ein Pre par edSt at ement (siehe nächsten Abschnitt) werden getXxx- bzw. setXxx-Methoden verwendet Hier findet eine Konvertierung von SQL-Typen in Java-Typen bzw. umgekehrt statt Bild 2,9 zeigt die Abbildung zwischen JDBC-Typen und Java-Typen, 2.3 43 ]DBC-Datentypen saa z». Abbildung zwischen ]DBC- und]ava- Typen JDBC-Typ Java-Typ Java-Objekttyp TINYINT byte Int eger SMALLINT short Inte ger INTEGER l nt Int eger BIGINT l ong Long REAL float Fl oat FLOAT double Doub 1e DOUBLE doubl e Doub 1e DECIMAL java.math Bi gl:€ci ma 1 ja va.math. Big Deci mal NUMERIC java.math Bigl:€ci ma 1 ja va.math. BigDecimal DATE java sql · Date ja va sql . Date TIME java sq l · Tü re java sql .Tlme TIMESTAMP java sql · Tl Jn2stamp ja va sql .Tlmestamp CHAR Str i ng Stri ng VARCHAR Stri ng Stri ng LONGVARCHAR Stri ng Stri ng BIT boolean Bool ean BINARY byte[] byte[] VARBINARY byte[] byte[] LONGVARBINARY byte[] byte[] Die get-Methoden der Klasse ResultSet sind dann entsprechend, byte getByte (...) short getShort(...) int getlnt(...) long getLong (...) floa t getFloat(...) doubl e getDouble(...) java. math. BigDeci ma1 getBi gDecima1 (...) java.sql.Date getDate(...) java.sql. Time getTime(...) java.sql. Timestamp getTimestamp( ..) String getString(...) boolean getBoolean(...) byte[] getBytes(...) getXxx 2 Datenbankanwendungen mit JDBC 44 Beim Aufruf einer get-Methode ist als Parameter eine Spaltennummer (Typ i nt) oder ein Spaltenname (Typ St ri ng) anzugeben. Alle get-Methoden können die Ausnahme SQLExcepti on auslösen. Die Resu ltSet-Methoden Object getObject(int column Index) throws SQLException Object getObject(String columnName) throws SQLException liefern den Spaltenwert als ein Java-Objekt. dessen Typ dem entsprechenden JDBC-Datentyp aus der obigen Tabelle (Bild 2.9) entspricht. So wird z.B. für einen Spaltenwert vom JDBC-Typ I NTEGE R ein Objekt vom Typ Int eger geliefert. Datum/Zeit Die Klassen Date, Time und Timestamp des Pakets ja va.sql sind Subklassen von java.util.Date und repräsentieren die JDBCDatentypen DATE. TIME und TIMESTAMP. Objekte dieser Klassen enthalten Datumsangaben. Uhrzeitangaben bzw. Angaben zu Datum und Uhrzeit. Date Date(long dat e ) erzeugt ein Date-Objekt mittels des Zeitwerts date in Millisekunden (seit 01.01.1970. 00,00,00 GMT). void setTime(long date) setzt das Datum. static Date valueOf(String s) liefert ein Date-Objekt aus der Zeichenkette mm-tt". 5 im Format "jjjj- String toString() liefert eine Zeichenkette im Format "jjjj mm-tt". Time Time(long time) erzeugt ein Time-Objekt mittels des Zeitwerts ti me in Millisekunden (seit 01.01.1970. 00,00,00 GMT). voi d setTime(long time) setzt die Uhrzeit. static Time valueOf(String s) liefert ein Time-Objekt aus der Zeichenkette "hh :mm: 55". String toString() liefert eine Zeichenkette im Format "hh :mm: ss". 5 im Format 2.4 Ausführung von SQL-Anweisungen 45 Timestamp(long t l me ) Timestamp erzeugt ein Tirrestamp-Objekt mittels des Zeitwerts time in Millisekunden (seit 01.01.1970. 00,00,00 GMT). void setTirre(long ti rre) setzt Datum und Uhrzeit. long getTirre() liefert Datum und Uhrzeit in Millisekunden (seit 01.01.1970, 00,00,00 GMT). static Timestamp valueOf(String s) liefert ein Tirrestamp-Objekt aus der Zeichenkette s im Format "jjjj- rrrn-tt hh:mm:ss.f", wobei f die Nanosekunden bezeichnet. Str ing toString() liefert eine Zeichenkette im Format "jjjj -rrrn-tt hh:mm: 55. f". boolean after( Tirrestamp t s ) prüft, ob diese Uhrzeit später als ts ist. boolean before(Timestamp ts) prüft, ob diese Uhrzeit früher als ts ist. int compareTo(Ti rrestamp ts) vergleicht diese Uhrzeit mit ts. boolean equals( Timestamp ts) prüft, ob dieses Timestamp-Objekt inhaltlich gleich dem TirrestampObjekt ts ist. 2.4 Ausführung von SOL-Anweisungen In diesem Abschnitt beschäftigen wir uns mit der Änderung von Datenbankstrukturen und -inhalten, der Abfrage von Metadaten über Ergebnismengen zur Laufzeit und mit der Schnittstelle PreparedStatement. 2.4.1 Transaktionen Im Fall von Änderungen steht der Begriff der Transaktion im Vordergrund. Eine Transaktion ist eine Folge von Datenbankoperationen Transaktion CSQL-Anweisungen), die die Datenbank von einem konsistenten Zustand in einen neuen konsistenten Zustand überführen. Die Anweisungen einer Transaktion werden entweder alle ausgeführt und abgeschlossen (committed] oder alle zurückgenommen Crolled back). Transaktionen können automatisch oder manuell gesteuert werden. Eine ]DBC-Anwendung ist standardmäßig im Auto-CommitModus, Jede einzelne SQL-Anweisung bildet eine Transaktion, die automatisch mit Commit bestätigt wird. 46 2 Datenbankanwendungen mit ]DBC Um mehrere Anweisungen innerhalb einer einzigen Transaktion ausführen zu können, muss Auto-Commit ausgeschaltet werden. Das Interface Connectlon deklariert Methoden zur Steuerung von Transaktionen. Auto-Commit void setAutoCommit(boolean autoCommit) throws SQLException schaltet Auto-Commitein (true) oder aus (false). boolean getAutoCommit() throws SQLException prüft. ob Auto-Commit eingeschaltet ist. Commitund Rollback void commit() throws SQLException zeigt an, dass die Transaktion abgeschlossen werden soll und alle Änderungen in der Datenbank permanent gespeichert werden sollen. void rollback() throws SQLException bricht die aktive Transaktion ab. alle bisherigen Änderungen innerhalb dieser Transaktion werden rückgängig gemacht. Transaktionen werden automatisch begonnen. Die erste SQLAnweisung nach einem Commit oder Rollback gehört schon zu einer neuen Transaktion. Eine manuell gesteuerte, noch nicht abgeschlossene Transaktion wird explizit mit der Methode commi t oder ro II back geschlossen. 2.4.2 Datenbankänderungen SQL-Anweisungen. die die Datenbankstruktur betreffen (z. B. CREATE TABLE) und die Änderungsbefehle INSERT. UPDATE und DELETE werden mit Hilfe der folgenden StatementMethode an die Datenbank geschickt int executeUpdate(String sql) throws SQLException Im Fall von INSERT. UPDATE oder DELETE liefert die Methode die Anzahl der von der Änderung betroffenen Zeilen. Programm 2.5 Programm 2.5 richtet die im Kapitel 2.2 eingeführten Tabellen verlag und buch ein. Die Properties-Datei tables.properties enthält die Verweise auf die entsprechenden SQL-Skripte. tables.properties #tab1es . ./ db/Access/ create_ver 1ag. sq1 # . ./ db/Access/ create_buch. sq1 t abl es .. /db/MySQL/create_verlag.sql .. /db/MySQL/create_buch.sql #tabl es . ./db/Derby/create_verl ag .sql # . ./ db/Derby/create_buch. sq1 2.4 Ausführung von SQL-Anweisungen 47 Das Programm ist unabhängig von einem konkreten Datenmodell und somit universell einsetzbar. lmport lmport lmport lmport lmport import lmport java.lo BufferedReader; java.lo Fl l eI nput St ream; java.lo. FlleReader; java sql.Connectlon; java sql.Drl verManager; java sql.SQLException; java sql.Statement; lmport java.utll.Propertles; lmport java.utll.StrlngTokenlzer; public class CreateTables ( public static voi d main(String[] args) { Connectlon con = null; try ( File lnputStream in ~ new File lnputStream( "dbconnect. propertles"); Properties dbProp dbProp.load (in); in.close() ; ~ new Properties() ; String url ~ dbProp.getPropert y("url ") ; String user ~ dbProp .getProperty("user". ""); String password ~ dbProp.getProperty("password". ""); con = Drl verManager.getConnectlon (url, user, password); in ~ new File lnputStream("tables.propert ies"); Propertles tablesProp = new Propertles(); tab 1es Prop .load (i n) ; in.close() ; StrlngTokenlzer tables = new StrlngTokenlzer(tablesProp .getProperty( "tab 1es")); while (tables.hasMoreTokens()) ( BufferedReader reader ~ new BufferedReader( new File Reader (t able s .next Token( )) ) ; Strlng l tne: StringBuilder sb ~ new StringBuilder(); whil e ((line ~ reader.readLine()) !~ null) sb.append(line); } reader .close(); Statement stmt = con.createStatement(); stmt.executeUpdate(sb.toString ()); CreateTables 2 Datenbankanwendungen mit JDBC 48 stmt.close(); } catch (Exception e) ( System.err.println(e); finally { try ( con.close() ; catch (SQLException e) System.err.println(e); In vielen Datenbanksystemen (wie auch bei MySQL) wird eine Transaktion durch create table automatisch abgeschlossen. Deshalb bleibt hier Auto-Commit eingeschaltet. Programm 2.6 Das folgende Programm ermöglicht das Laden von Tabellen aus externen Quellen. Importdatei Die Importdatei enthält pro Zeile einen in die Tabelle einzutragenden Datensatz, wobei die Spaltenwerte in der gleichen Reihenfolge auftreten, in der die Tabellenspalten mit der SQLAnweisung "CREATE TABLE" definiert "WUrden. Die Werte in der Datei sind durch ein Sonderzeichen (z. B.; oder das Tabulatorzeichen), das in keinem der Werte enthalten sein darf, getrennt (Feldtrenner). Beispiel, Die Importdatei für die Tabelle verlag hat den Inhalt. 1;Anaconda; IN 2;Artemis &Winkler;www.patmos de 3;Aufbau;www.aufbau- verlag.de 4;Dlogenes;www.dlogenes.ch Feldtrenner ist hier;. Evtl. nicht vorhandene Werte sind durch \N zu kennzeichnen. Diese entsprechen den NULL-Werten in der Datenbank Jede Zeile enthält drei Werte, die den Spalten uerlagid, verlag_name und webadresse entsprechen. Es folgen einige Konventionen für die Notierung der Werte in der Importdatei • Zeichenketten werden ohne Begrenzer wie z. B. " eingegeben. • Numerische Werte werden wie Literale in Java notiert. usw. 2.4 Ausführung von SQL-Anweisungen • Nicht vorhandene Werte (NULL-Werte) werden durch IN gekennzeichnet. • Formate für Datum, Uhrzeit und Zeitstempel sind "jjjj- rrrntt", "hh: rrrn:ss" bzw. "jjjj- rrrn-tt hh:mm:ss". 49 Das Programm ist allgemeingültig geschrieben und kann zum Laden beliebiger Tabellen genutzt werden. Es wird ntit drei Parametern aufgerufen: 1. Name des Verzeichnisses, in dem sich die Importdateien be- finden C". " kennzeichnet das aktuelle Verzeichnis) 2. Feldtrenner, z. B. ; oder It (falls das Tabulatorzeichen benutzt wurde) 3. Name der Tabelle Der Name der Importdatei für die Tabelle xxx muss dann xxx.txt lauten. Das Programm generiert für jede eingelesene Zeile der Importdatei eine INSERT-Anweisung. Da die Tabellenstruktur (insbesondere die verwendeten SQL-Datentypen) dem Programm zur übersetzungszeit nicht bekannt ist, müssen Informationen hierüber zur Laufzeit abgefragt werden. Hierzu wird die ResultSet-Methode get l'etaData verwendet. die ein Resu ltSetl'etaData-Objekt bereitstellt. das die gewünschten Informationen enthält ResultS etMetaData getMetaData() throws SQL Exception Das Interface Resul tSet rvetaData deklariert u. a. Methoden, um Resu!tSetMetaData den ]DBC-Datentyp einer Spalte. die Anzahl der Spalten und einen Spaltennamen der Ergebnisrelation abzufragen. int getColumnType(int column) throws SQL Exception int getColumnCount() throws SQLException String getColumnLabel (int column) throws SQLException lmport lmport lmport lmport lmport import import import lmport import java.lo BufferedReader; java.lo Fl leI nput St ream; java.lo. Fll eReader; java sql.Connectlon; java sql.DrlverManager; java sql.Res ultSet; java sql.Res ultSetMetaData; java sql.SQLException; java sql.Statement; java sql. Types; Import 2 Datenbankanwendungen mit ]DBC 50 lmport java utll.Propertles; lmport java util.StringTokenizer; public class Import ( public static void main(String[J args) ( String dir ~ args[OJ; String delimiter ~ args[IJ; String table ~ args[2J; i f (deli miter .equa1s ( "I I t" ) ) delimiter ~ "It"; BufferedReader tab Connection con try ( tab = ~ null; null; new BufferedReader(new FileReader(dir + "j" ~ + table + ".txt")); FileInputStream in ~ new File InputStream( "dbconnect.properties"); Properties prop = new Properties(); prop.load(in) ; in.close(); Stri ng ur 1 ~ prop. getProperty(" ur 1"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); con Dri verManager .getConnecti on(ur1, user, password); = con.setAutoCommit(false); Statement stmt = con.createStatement(); // Ermittlung der Metadaten zur Feststellung der /I Spaltentypen ResultSet rs = stmt. executeQuery( "se1ect * fram " + table + " where 0 ~ I"); ResultSet MetaOata rsmd ~ rs.get Me taOata(); StringTokenizer st; String line; while ((line ~ tab.readLine()) if (line.length() ~~ 0) !~ null) ( continue; st = new StringTokenizer(line, delimiter); Stri ngBui 1der sq 1 ~ new Stri ngBui 1der (" insert i nto " + table + " values ("); String s; i nt i = 0; while (st hasMoreTokens()) ( i++" 2.4 Ausführung von SQL-Anweisungen 51 s ~ st.nextToken(); sq1. append (getVa1ue(rslld.getCo1umnType (i ). s)); if (st.countTokens() !~ 0) sq1. append (" . " ) ; ) sql. append(")"); stmt.executeUpdate(sql.toString()) ; stmt. cl ose () ; con. corrrnlt () ; System.out.println("Daten wurden in Tabelle '" + '" + table lmportlert"); catch (Exception e) ( System.err.println(e); try { if (con !~ null) con. ro11 back() ; catch (SQLException ex) System.err.println(ex); ) finally { try { if (con !~ null) con.close(); if (tab !~ null) tab.close(); catch (Exception e) ( System.err.println(e); // typgerechte Aufbereitung der Spaltenwerte für INSERT private static String getValue(int type. String s) { / / aus IN wi rd null if (s.equals("IIN")) re turn "null"; else if (type ~~ Types.CHAR 11 type ~~ Types.VARCHAR 11 type ~~ Types LDNGVARCHAR) { // der einfache Anführungsstrich ' wird verdoppelt s ~ s. replaceAll (" "'. """); return "'" + 5 + "'''; else if (type ~~ Types.DATE) return "{d '" + s + "')"; else if (type ~~ Types.TIME) return "{t '" + s + "')"; else if (type ~~ Types.TIMESTAMP) { // bei Access entsprechen die SQL-Typen DATE. TIME. // DATET IME alle dem JDBC-Typ TIMESTAMP 2 Datenbankanwendungen mit ]DBC 52 if (s.indexOf(' ') !~ -1) return "( ts '" + s + "'}"; else if (s.indexOf('-') !~ -1) return "{d + s + "'}"; else return "{t + s + "'}"; else return 5; Um Informationen über die ]DBC-Datentypen der Tabellenspalten zu erhalten, wird zunächst eine SQL-Abfrage der Form select * from tabelle where 0 ~ 1 ausgeführt, die eine leere Ergebnismenge liefert. Die Resu ltSet-Methode getMetaData liefert dann ein Resu ltSetMetaData-Objekt. Mit Hilfe von StrlngTokenlzer wird nun jede Zeile in ihre Werte zerlegt und die INSERT-Anweisung aufgebaut. lnsert into tabelle values (wertl, ... ) Die Hilfsmethode getValue(int type, String s) sorgt für die SQL-konforme Aufbereitung des eingelesenen Wertes. Hier liegen die folgenden Regeln zugrunde, Ist 5 gleich "\N", so liefert die Methode "null" ZUIÜCk. Ist der ]DBC-Typ type gleich CHAR, VARCHAR oder LONGVARCHAR, wird der Wert in einfache Hochkommata eingeschlossen. Ein einfaches Hochkomma • im Wert selbst wird verdoppelt. Ist type gleich DATE, TIME oder TIMESTAMP, so werden EscapeKlauseln der Form {d 'xxx'}, {t 'xxx'} bzw. {ts 'xxx'} zurückgeliefert, wobei xxx für das Literal für Datum, Zeit bzw. Zeitstempel steht (siehe obige Beschreibung der Importdatei). Test Wegen der bestehenden Fremdschlüssel-Primärschlüssel-Beziehung muss zuerst die Tabelle verlag, dann die Tabelle buch geladen werden. Batch-llpdates Mehrere INSERT- oder UPDATE-Anweisungen können in einem Staterrent-Objekt mit addBatch gesammelt werden und dann in einem Zug mit executeBatch ausgeführt werden. Diese Vorgehensweise ist in der Regel effizienter als die einzelne Ausführung von SQL-Anweisungen. 2.4 Ausführung von SQL-Anweisungen 53 Hier die beiden Staterrent-Methoden, void addBatch(String sql) throws SOLException int[J executeBatch() throws SOLException Das i nt-Array enthält für jede SQL-Anweisung die Angabe darüber, wie viele Datensätze eingefügt bzw. verändert "WUrden, den Wert Statement.SUCCESS NO INFO oder Staterrent.EXECUTE FAILED im Fehlerfall. Das nächste Programm exportiert die Daten einer Tabelle im Programm2.7 oben beschriebenen Import-Format. Damit können die Daten der Datenbank in einem Textformat gesichert werden. lmport import import import import import import import java.ia FileInputStream; java.ia FileWriter; java.io.PrintWriter; java sql.Connection; java sql.DriverManager; java sql.ResultSet; java sql.ResultSetMetaData; java sql.Statement; Export import java.utll.Properties; public class Export ( public static void main(String[J args) { String dir ~ args[DJ; String delimiter ~ args[IJ; String table ~ args[2J; if (delimiter.equals("llt")) delimiter ~ "It"; Pri ntWrHer out = null; Connection con = null; try ( out = new PrintWrHer(new FlleWrlter(dir + ".txt"). true); FileInputStream in ~ + "I" + table new FileInputStream( "dbconnect. properties"); Propertles prop prop.load (i n) ; in.close() ; = new Propertles(); String url ~ prop.getProperty("url"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); con = DrlverManager.getConnectlon(url, user, password); 2 54 Statement stmt = Datenbankanwendungen mit ]DBC con.createStatement(); ResultSet rs = stmt. executeQuery( "se1ect * from " +table); ResultSet MetaData rsmd ~ rS.getMetaData(); int n ~ rsmd.getColumnCount(); whi 1e (rs. next ()) { for (int i ~ 1; i <~ r.: i++) ( String val ue ~ rS.getString(i); if (rs.wasNull()) out.print("\\N"); else ( out.print( value); } i f (i < n) out print(delimiter); } out println(); System.out.println("Tabelle '" + table + '" wurde exportlert"); stmt.close(); catch (Exception e) ( System.err.println(e); finally { try { if (con !~ null) con.close() ; i f (out !~ null) out.close() ; catch (Exception e) ( System.err.println(e); Nullwert abfragen Um feststellen zu können, ob eine Spalte der Ergebnisrelation keinen Eintrag hat (SQL-Wert NULL), muss zunächst mit einer getXxx-Methode auf die Spalte zugegriffen werden. Dann kann mit der folgenden ResultSet-Methode abgefragt werden, ob der letzte gelesene Wert ein Nullwertwar: boolean wasNull() throws SQLException 2.4 2.4.3 Ausführung von SQL-Anweisungen Prepared Statements Wird ein und dieselbe SQL-Anweisung mehrfach (mit verschiedenen Parameterwerten) ausgeführt, so können Laufzeitvorteile dadurch erzielt werden, dass statt eines Staterrent-Objekts ein Objekt vom Typ des Subinterfaces PreparedStatement verwendet wird. Ein PreparedStatement-Objekt wird durch Aufruf der Connectiorc Methode prepareStatement erzeugt PreparedStatement prepareStatement(String sql) throws SQLException Der String sq1 wird zum DBMS geschickt, dort analysiert und für die Ausführung vorbereitet. Evtl. Parameterwerte werden in diesem String nicht angegeben, sondern nur durch den Platzhalter? gekennzeichnet. Somit wird der String vom DBMS nur einmal geparst und auf Korrektheit geprüft. Bei jeder späteren Ausführung werden nur noch die Parameterwerte gefüllt. Analog zu den getXxx-Methoden von ResultSet (siehe Kapitel set.Xxx 2,3) stellt PreparedStatement se tXxx-Methoden bereit, mit denen die Parameterwerte gesetzt werden können. void void void void void void void void void setByte(int idx. byte x) setShort(int idx. short x) setlnt(int idx. int x) setLong(int idx. long x) setFloat(int idx. float x) setDouble(int idx. double x) setBigDecimal(int idx. BigDecimal x) setDate(int idx. Date x) setTime(int idx. Time x) vOld setTimestamp(lnt ldx, Tlmestamp x) void setString(int idx. String x) vOld setBoolean(lnt ldx, boolean x) void setBytes(int idx. byte[] x) l dx kennzeichnet den zu ersetzenden Parameter mit Hilfe seiner Positionsnummer, von links nach rechts, beginnend mit 1. Alle set-Methoden können eine SQLExceptl on auslösen. voi d setDbject(int idx. Object val ue ) throws SQLException setzt den Wert des Parameters an der Stelle i dx mit Hilfe des Objekts obj. Dabei wird val ue in einen Wert des entsprechenden SQL-Typs konvertiert (siehe Bild 2.8 und 2.9). Nullwerte können mit setNull übergeben werden, wobei eine passende Konstante aus java.sql.Types anzugeben ist: 55 56 2 Datenbankanwendungen mitJDBC vo4d setNull(4nt parameterIndex, 4nt sqlType) throws SQLExcept40n Das Interface PreparedStatement enthält auch die entsprechenden Methoden zur Ausführung der SQL-Anweisung: ResultSet executeQuery() throws SQL Except40n 4nt executeUpdate( ) t hrows SQLExcept40n vo4d clearParameters() throws SQLExcep t40n löscht die aktuellen Parameterwerte und gibt damit Ressourcen frei. Batch-Updates Batch-Updates können auch mit PreparedSt at ement ausgeführt werden. Mit Hilfe der PreparedSt at ement -Methode vo4d addBatch() throws SQLExcept40n werden Parametersätze, die mit den set Xxx-Methoden angelegt 'WUrden, gesammelt. Die einzelnen Operationen werden dann wieder am Schluss in einem Zug mit Hilfe der Methode executeBatch (siehe oben) ausgeführt. Programm 2.8 Das folgende Programm nutzt ein PreparedSt at ement , um den Lagerbestand zu erhöhen bzw. zu vermindern. Die folgende UPDATE-Anweisung wird verwendet: update buch se t bes t and where 4sbn = ? = bestand ?, stand + = ? ISBN-Nummer und Menge werden aus der Eingabedatei bestand. txt gelesen. Bild 2.10 zeigt den Datenbankzustand vor der Änderung. Büd 210: Update des Lagerbestands 3- 1 5-00 1308-9 3-2 57- 20998-3 3-3 5 1-030 44- 4 bestand.txt -5 -4 7 tz 3-15-001562-6 "zn " 3-257-20998-3 1 3-257-21034-5 ;; 3-257-21166-1( 3-257-21406-5 a aa e 3-351-03044-4 C; 3-458-32655-3 3-458-32733-9 ,an 3-458-3281G-6 ,C 3-458-33004-6 zn tu 3-257-21405-7 OB .> "" stond 3-15-001308-9 3-15-010606-0 ~ UpdateBestand " isbn 3-491-96007-1( 3-538-05349-9 3-538-06656-6 ; " CC 2.4 Ausführung von SQL-Anweisungen lmport import import import import import import import java.ia BufferedReader; java.la FileInputStream; java.io.FileReader; java sql.Connection; java sql.Dri verManager; java sql.PreparedStatement; java sql.SQLException; java sql.Ti mestamp; import java.utll.Properties; import java.utll.StringTokenizer; public class UpdateBestand ( public static void main(String[] args) { String datei ~ "bestand.txt"; BufferedReader input ~ null; Connection con = null; try ( input ~ new BufferedReader(new FileReader(datei)); FilelnputStream in ~ new FilelnputStream ( "dbconnect. properties"); Properties prop prop.load(in) ; in.close() ; = new Properties(); String url ~ prop.getProperty("url"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); con = DriverManager.getConnection(url, user, password); con setAutoCommit(false); PreparedStatement stmt = con.prepareStatement( "update buch set bestand ~ bestand + ? + "stand = ? where isbn = ?"); String isbn; i nt bestand; i nt count; StringTokenizer st; String line; while ((line ~ input.readLine()) !~ null) ( st = new StringTokenizer(line); isbn ~ st.nextToken(); bestand ~ Integer.parselnt(st.nextToken()); stmt.setlnt(l. bestand); stmt.setTi mestamp(2. new Timestamp(System .currentTi me Hi 11 i s ())) ; stmt setString(3. isbn); 57 UpdateBestand 2 Datenbankanwendungen mit ]DBC 58 count ~ stmt executeUpdate(); i f (count > 0) System.out.println(isbn + "; Bestand aktualisi ert"); con .commlt ( ) ; stmt.close(); catch (Exception e) ( System.err.pr i ntln(e); try { if (con !~ null) con. rollback(); catch (SQL Exception ex) System. err .println(ex); } finally { try { if (con !~ null) con.close() ; if (i nput !~ null) i nput. cl ose () ; catch (Exception e) ( System.err.println(e); 2.5 Ein einfaches Frontend für SOL-Datenbanken In Kapitel 2.2 wurden bereits Metadaten über die Datenbank (OatabaseMetaData) genutzt. Hier folgen zwei weitere Methoden: ResultSet getTables(String catalog . String schemaPattern. String tableNamePattern. String[] types) throws SQLException liefert eine Beschreibung der Tabellen der Datenbank. Im Beispiel rufen wir die Methode mit den Parametern null , null, "%", null auf. Das ResultSet-Objekt enthält u. a. die Spalten TABLE- NAME und TABL E- TYP E. ResultS et getColumns(String catalog. String schemaPattern. Str ing tableNamePattern. String col umnNamePattern) throws SQLException liefert eine Beschreibung der Tabellenspalten. Im Beispiel rufen wir die Methode mit den Parametern null, null, Tabellenname, "Z" auf. Das ResultSe t-Objekt enthält u. a. die Spalten 2.5 Ein einfaches Frontend für SQL-Datenbanken COLUMN_NAME, DATA_TYPE TYPE_NAME, COLUMN_SIZE. ODBC-Datentyp aus java sql. Types), Das folgende Programm bietet ein Frontend für SQL-Daten- Programm2.9 banken, mit dem beliebige SQL-Anweisungen (SELECT, INSERT, UPDATE, DELETE) an eine Datenbank geschickt werden können. Die Abfrageergebnisse können auf Wunsch in der Datei log.txt protokolliert werden. Das Programm bietet neben den SQLAnweisungen noch die folgenden Eingabemöglichkeiten show tables alle Tabellennamen anzeigen show table xxx Metadaten zur Tabelle xxx anzeigen log on Protokollierung einschalten log off Protokollierung ausschalten q Programm beenden In diesem Programm nutzen wir die Staterrent-Methode Unbekannte execute, um beliebige, zur Compilierungszeit unbekannte SQL- Anweisungen Anweisungen an die Datenbank zu schicken: ausführen boolean execute(String sql) throws SOLException Der Rückgabewert ist true, wenn ein Abfrageergebnis vorliegt, und false, wenn es sich um eine Änderung handelt. Mit den Statement-Methoden getResultSet und getUpdateCount kann die Ergebnismenge bzw. die Anzahl der geänderten Zeilen ermittelt werden: ResultSet getResultSet() throws SOLException int getUpdateCount() throws SOLException Auch die Schnittstelle PreparedStatement enthält eine executeMethode, boolean execute() throws SOLException Bei schwerwiegenden Fehlern Methoden einen Fehler durch java. sq1 .SOLExcepti on (Subklasse lermeldungen können in einem verpackt sein. melden die meisten ]DBC- SQLException eine Ausnahme der Klasse von Excepti on). Mehrere FehObjekt vom Typ SOLException SOLExcepti on enthält folgende Methoden, 59 2 Datenbankanwendungen mit ]DBC 60 String getSQLState() liefert eine Fehlerbeschreibung nach X!OPEN- bzw. SQL,2003Konventionen. int getErrorCode() liefert einen herstellerspezifischen Fehlercode. SQLException getNextException() liefert das nächste Ausnahme-Objekt, falls mehrere Fehlermeldungen vorliegen. Sql lmport lmport lmport lmport lmport lmport lmport l mport l mpor t import import import lmport lmport java java java java java java ja va ja va java java java java java java lO.BufferedReader; lo.Flle InputStream; lO.FlleWrlter; lo.IOExceptlon; lo.InputStreamReader; lO.PrlntWrlter; sql .Connectlon: sq1 . DatabaseMetaData; sq1 . Drl verManager ; sql.ResultSet; sql.ResultSetl'etaData; sql .SQLException; sql.Staterrent; util.Properties; public class Sql private static private static private static private static ( Connection con; boolean log; BufferedReader input; PrintWriter output; public static void main(String[] args) try ( FilelnputStream in ~ new File lnputStream( "dbconnect.properties"); Properties prop = new Properties(); prop.load(in) ; in.close(); Stri ng ur1 ~ prop. getProperty(" ur1"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); con = DriverManager.getConnection(url, user, password); input ~ new BufferedReader(new InputStreamReader( System. in)); String query; 2.5 Ein einfaches Frontend für SQL-Datenbanken whi 1e (true) ( System.out.print("> "); query ~ input.readLine(); if (query.equals("q")) break; if (query.equals("log on")) if (output ~~ null) ( output = new PrlntWrlter( new FileWriter("log.txt"). true); } log ~ true; contlnue; if (query.equals("log off")) log ~ false; contlnue; i f (log) output.println ("Query; " + query); try ( process(query) ; catch (SQLException e) printSQLException(e); } catch (Exception e) ( System.err.println(e); finally { try { if (con !~ null) con.close(); if (input !~ null) input.close(); if (output !~ null) output. cl ose () ; catch (SQLException e) printSQLException(e); catch (IQException e) System.err.println(e); 61 62 2 Datenbankanwendungen mit ]DBC private static void printSQLException(SQLException e) ( System. err. pri nt1n( "SQLExcepti on: ") : while (e !~ null) ( System. err. pri nt1n( "SQLState: " + e. getSQLState ()): System.err.println("Message: + e.getMessage()): System. err. pri nt1n( "ErrorCode: " + e. getErrorCode ()): e ~ e.getNextException(): prlvate statlc vOld process(Strlng query) throws SQLException { if (query.equals("show tables")) showTables(): return; if (query.startsWith("show table ")) ( String table ~ query.substring(ll).trim(): showTable(table): return; Statement stmt = con.createStatement(); boolean lsResultSet = stmt.execute(query); if (isResultSet) ( ResultSet rs ~ stmt.getResultSet(): ResultSet MetaData rsmd ~ rs.get MetaData(): int numCols ~ rsmd.getColumnCount(): l nt count = 0; whi 1e (rs. next () ) count++ ; System.out.println("#" + count ): if (log) output prin tln("#" + count ): for (int i ~ 1: i <~ numCols: i++) ( String line ~ rsmd.getColumnLabel(i) + ": + rs.getString(i): System.out.println(line): i f (log) output.println(line): if (log) output pri nt1n(): rs.close(): 2.5 63 Ein einfaches Frontend für SQL-Datenbanken else ( int updateCount ~ stmt.getUpdateCount(); System.out. pri nt 1n(" UpdateCount ; " + updateCount ); if (log) { output. pri nt 1n(" UpdateCount ; + updat eCount); output.println(); } stmt. cl ose () ; pri vat e static void showTables() throws SQLException ( DatabaseMetaData dbmd ~ con.g etMetaData(); ResultSet rs ~ dbmd.getTabl es(null. null. "%". null); String tabl e. type; while (rs.next()) ( t abl e ~ rs.getString("TABL E~NAME"); t ype ~ rs.getS tring("TABL E~TYP E" ); System.out.println (type + u. " + table); i f (log) output.print ln(type + ": + table); i f (log) output.println(); rs .close(); private stat ic void showTable(String table) throws SQLException ( DatabaseMetaData dbmd ~ con.g etMetaData(); ResultSet rs ~ dbmd.getColumns(null. null. table. "%") ; Strlng name, type, s lze; while (rs.next()) ( narre ~ rs.getS tring("COLUMN~NAME"); t ype ~ rs.getS tring("TYP E~NAME"); size ~ rs.getString("COLUMN~SIZ E"); System.out.prlntln (name + " " + type + "(" + slze + '')'' ) ; i f (log) output.prlnt ln(name + " " + type + "(" + s i ze + ")"); i f (log) output.println(); rs .close(); 64 Test mit MySQLDatenbank 2 Datenbankanwendungen mit ]DBC Ja va cp b,n %JDBC DR IVER% Sql > log on > show tables TABLE: buch TABLE: verl ag > show table buch isbn VARC HAR( 17) autor VARCHAR(30 ) titel VARCHAR(80) ausgabe VARCHAR(20) sei tenzah I INT( 10) jahr INT( 10) ver l ag_id I NT(10) preis DOUBL E(22) bestand I NT(l O) stand DATET IME(19) > select * from buch where titel like '%Twist%' # 1 i sbn: 3-7466-2200-X autor: Dlckens, Charles titel: Oliver Twist ausgabe: Kartonlert sei t enzahI: 564 jahr: 2005 verlag_id : 3 preis: 9.95 bestand: 50 stand: 2010 02-07 00:00:00.0 > log off > q 2.6 Speicherung großer Objekte Wir wollen nun die Struktur der Tabelle buch um eine zusätzliche Spalte, die ein Bild des Covers aufnehmen kann, ergänzen. Die hierfür geeignete SQL-Anweisung lautet ""ccess: alter tabl e buch add bild longbinary MySQL: alter table buch add bild longblob Apache Derby: alter table buch add bild blob Große Binärobjekte schreiben und lesen Mit Streams können die Daten blockweise geschrieben bzw. gelesen werden. PreparedState rrent-Methode: vOl d setBinaryStream(lnt parameterIndex, Input St ream x, int length) throws SQLException 2.6 Speicherung großer Objekte 65 1ength ist die Größe der Datei in Byte. Resu1tSet-Methoden, InputStream getBinaryStream(int columnlndex) throws SQLException InputStream getBinaryStream(String columnName) throws SQLException ImportImage speichert ein jpeg-Bild für eine bestimmte ISBN- Programm2.10 Nummer. Die Bilder sind in einem vorgegebenen Verzeichnis zu finden und tragen jeweils den Dateinamen: <l sbn>. jpeg. Aufrufbeispiel java -cp bin %JDBC DRIVER% ImportImage bilder 3-15-001308-9 lmport lmport lmport import import import ja va.lo Flle; java.lo FlleInputStream; java.io.InputStream; java sql.Connection; java sql.DriverManager; java sql.PreparedStatement; ImportImage import java.utll.Properties; public class Import Image ( public static void main(String[J args) ( String dir ~ args[OJ; String isbn ~ args[IJ; Input St ream fi n ~ null; Connection con = null; try ( FilelnputStream in ~ new FilelnputStream( "dbconnect. properties"); Properties prop prop.load (i n) ; in.close() ; = new Properties(); String url ~ prop.getProperty("url"); String user ~ prop.getProperty("user". ""); String password ~ prop.ge tProper ty("password". ""); con = DriverManager.getConnection(url, user, password); String image = dir + "I" + isbn + File file ~ new File(image); int length ~ (int) file.length(); fin ~ new FilelnputStream(file); Jpeg 66 2 Datenbankanwendungen mit ]DBC PreparedSt at ement ps = con.prepareStatement( "update buch set bild ~ ? where isbn ~ ?"); ps setBinaryStream( l, fi n, length ); ps setString(2, isbn); int count ~ ps executeUpdate(); i f (count> 0) System.out.prl ntln(lmage + " wurde lmportlert"); ps.cl osct ): catch (Exception e) ( System,err,println(e); f inally { try { if (con !~ null) con.close t ) ; i f (fin !~ null) f iu.cl ose O ; catch (Except i on e) ( System, err ,println(e); Programm 2.11 Mit ExportImage kann ein solches gespeichertes jpeg-Bild wieder in ein Dateiverzeichnis exportiert werden. ExportImage lmport lmport lmport lmport l mport import lmport ja va ja va ja va ja va ja va ja va ja va lo. Flle InputStream; lo. FlleOutputStream; lo. InputStream; sql.Connectlon; sq1 . Drl verManager ; sql .ResultSet : sql.Statement; lmport ja va utll.Propertles; public class ExportImage ( public stat ic void main(String[J args) { Str ing dir ~ args[OJ; Str ing isbn ~ args[ IJ ; Input St ream fin ~ null; Fi l eOut put St ream fout ~ null; Connectl on con = null; try { Fi l el nput St ream in ~ new Fi l el nput St ream( 2.6 67 Speicherung großer Objekte "dbconnect. propertles"); Propertles prop = new Propertles(); prop.load(in) ; in.close() ; String url ~ prop.getProperty("url"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); con = DrlverManager.getConnectlon(url, user, password); Statement stmt = con.createStatement(); ResultSet rs ~ stmt .executeQuery( "sel ect bi l d fram buch where i sbn + isbn + '" and bi Idis not null"); = '" i f (rs. next ()) ( fin ~ rS.getBinaryStream(l); Strlng lmage = di r + "I" + i sbn + Jpeg fout ~ new FileOutputStream(image); byte[] buffer ~ new byte[1024]; int si ze : while ((size ~ fin.read(buffer)) !~ -1) { fout. wri te (buffer. O. si ze); ) fout. fl ush () ; System. out. pr i nt ln (" Bll d wurde exportl ert: " + l mage); else System. out. pri nt In (" Kei n BiI d vorhanden"); rs .close(); stmt. cl ose () ; catch (Exception e) ( System.out.println(e); finally { try { if (con !~ null) con.close(); if (fin !~ null) fin.close(); if (fout !~ null) fout. cl ose () ; catch (Exception e) ( System.err.println(e); 2 Datenbankanwendungen mit ]DBC 68 2.7 Navigation und Änderungen inder Ergebnismenge Bei der Erzeugung von SELECT-Anweisungen kann auf die Art der Verwendung der Ergebnismenge Einfluss genommen werden. Hierzu existieren die beiden Connect l on-Methoden Statement createStatement (int resultSetType, lnt resultSetConcurrency) throws SQLException PreparedSt at ement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException Der Parameter resultSetType bestimmt, wie in der Ergebnismenge mit Hilfe eines internen Cursors navigiert werden kann. Der Parameter resu l tSetConcurrency legt fest, ob Datensätze der Ergebnismenge direkt geändert werden können. Navigierbarkeit Für resultSetType können folgende Konstanten verwendet wer- den, ResultSet, TYP E- FORWARD- ONLY Der Cursor kann nur zum nächsten Satz bewegt werden. Dies ist die Standardeinstellung für die parameterlose Connectlon-Methode createStatement. ResultSet, TYP E- SCROLL - INSENSITI VE Der Cursor kann frei bewegt werden. ResultSet,TYP E- SCROLL - SENS ITIVE Der Cursor kann frei bewegt werden. Änderungen durch andere Transaktionen sind sichtbar. Änderbarkeit Für resu ltSetConcurrency können folgende Konstanten verwendet werden: ResultSet,CONCUR READ ONLY Änderungen können nicht vorgenommen werden. Dies ist die Standardeinstellung für die parameterlose Connect ion-Methode createStatement . ResultSet,CONCUR UPDATABL E Datensätze können in der Ergebnismenge geändert werden. Die freie Navigation kann bei entsprechender Einstellung mit den folgenden ResultSet-Methoden erfolgen : boo1ean next() boolean prevlous() boolean first() boo1ean 1ast() boolean absolute(int row) 2.7 Navigation und Änderungen in der Ergebnismenge boolean relative(lnt rows) void beforeFirst() void beforeLast() Der Cursor wird zur nächsten, vorherigen, ersten, letzten Zeile bewegt, auf die Zeile mit der Nummer row positioniert (die Zählung beginnt bei 1), um rows Zeilen vorwärts bzw, rückwärts (bei negativer Zahl) bewegt, vor die erste Zeile, hinter die letzte Zeile bewegt Ferner existieren die Prüfmethoden: boolean boolean boolean boolean isFirst() isLast() isBeforeFirst() isAfterLast() int getRow() liefert die Nummer des aktuellen Datensatzes Cl für den ersten Satz), Das folgende Programm demonstriert die verschiedenen Mög- Programm2.12 lichkeiten der Navigation durch die Ergebnismenge. lmport lmport lmport lmport lmport import import lmport lmport java.lo BufferedReader; java.lo Fll eI nput St ream; j ava. jo . InputSt reamReader ; java sql.Connectlon ; java sql.Drl verManager; java sql.ResultSet; java sql.SQLExcep tion; java sql.Statement; java.utll.Propertles; public class Na vigation ( public static void main(String[] args) { Connectlon con = null; Statement stmt ~ null; ResultSet rs ~ null; try ( Fi l elnput St r eam in ~ new File lnputStream( "dbconnect.propertles"); Propert les prop = new Propertles (); prop . load (i n) ; in.close() ; String url ~ prop. get Propert y( "ur l "); String user ~ prop.getProperty("us er". "" ); String password ~ prop.getProperty("password". ""); con = DrlverManager.getConnectlon(url, user, password); 69 2 Datenbankanwendungen mit ]DBC 70 stmt = con.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE. ResultSet.CONCUR_READ_ONLY); rs = stmt.executeQuery( "select isbn. titel from buch order by isbn"); if (rs.last()) ( System.out.println("Anzahl Zeilen; " + rs.getRow()); else ( System. out. printl n( "Ergebnl smenge i st leer"); return; } rs. beforeFi rst(); System.out.println("Ihre Eingabe bitte; " + "next , prevl aus, fl rst . l ast oder qult"); BufferedReader br ~ new BufferedReader( new InputStreamReader(System. in)); Strlng I i ne ; whi 1e (( 1i ne ~ br readLi ne ()) ! ~ null) try { if (line.equals("next")) ( if (rs.next()) System.out.println(rs.getRow() + " " + rs.getString(1) + " " + rs.getString(2)); else if (line.equals("previous")) ( if (rs.previous()) System.out.println(rs.getRow() + " + rs.getString(1) + " " + rs.getString(2)); else if (line.equals("first")) ( if (rs.first()) System.out.println(rs.getRow() + " " + rs.getString(1) +"" + rs getString(2)); else if (line.equals("last")) ( if (rs .last()) System.out.println(rs.getRow() + " " + rs.ge tString(1) +"" + rs getString(2)); else if (line.equals("quit")) { break; } catch (SOLException e) System.err.println(e); } catch (Exception e) ( System.err.println(e); 2.7 Navigation und Änderungen in der Ergebnismenge 71 finally { try { if (rs !~ null) rs .close(); if (stmt !~ null) stmt. cl ose () ; if (con !~ null) con.close(); catch (SQLException e) { Ist die Ergebnismenge als änderbar eingestellt, können Datensätze direkt in einem ResultSet-Objekt eingefügt, geändert und gelöscht werden. In der Regel darf die SELECT-Abfrage nur eine Tabelle umfassen (keine JOINs) und keine GROUP-Klausel enthalten. Die Abfrage muss den Primärschlüssel mit einschließen. Die genauen Regeln hängen vom DBMS ab. Für das Folgende setzen wir voraus, dass der Cursor auf einem bestimmten Datensatz der Ergebnismenge positioniert ist. Aufruf der Resu ltSet-Methoden Ändern void updateXxx(int idx. typ x) throws SQLException analog zu den setXxx-Methoden aus Kapitel 2.4.3, also z. B. rs.updatelnt(3. 100); Aufruf der Resu ltSet-Methode void updateRow() throws SQLException Aufruf der Resu ltSet-Methode void moveTolnsertRow() throws SQLException Einfügen Aufruf der update Xxx-Methoden. Aufruf der Resu ltSet-Methode void insertRow() throws SQLException Ggf. Aufruf der ResultSet-Methode vOld moveToCurrentRow() throws SQLExceptlon um zum aktuellen Datensatz zurückzukehren, Aufruf der Resu ltSet-Methode void deleteRow() throws SQLException Löschen 2 Datenbankanwendungen mit ]DBC 72 Programm 2.13 Das folgende Programm demonstriert die drei Änderungsarten. Zu einem Buch kann die Bestandszahl geändert werden, ein neues Buch mit ISBN-Nummer kann eingefügt werden, ein Datensatz kann gelöscht werden. l mport lmport lmport lmport l mpor t import import java ja va ja va ja va java java ja va lO. BufferedReader; lo. Fll e InputStream; lo. InputStreamReader; sql .Connectlon: sql . Drl verManager ; sql. ResultSet; sql .SQL Exception; lmpor t ja va sql.Staterrent ; lmport ja va sql.Tlmestamp ; lmport ja va util.Properties; publ ic class Update ( public static voi d main(String[] args) ( Connection con = null; Staterrent stmt ~ null; ResultSet rs ~ null; try ( Fi l el nput St ream in ~ new Fi l elnput St r eam( "dbconnect.properties"); Properti es prop = new Proper ties(); prop.load( in) ; in.clos e(); Stri ng ur1 ~ prop. getProperty(" ur1"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); con = Dri verManager .getConnection (url, user, password); stmt = con.createStatement( ResultSet. TYP E_SCROLL_INS ENS ITIVE. ResultSet.CONCUR_UPDATA ßL E); rs = stmt.executeQuery( "select isbn. titel. bestand. stand from buch"); if (rs.last()) ( System. out pri nt 1n( "Anzah1 Zei1en; " + rs. getRow()) ; else ( System. out pri nt l n( "Ergebnl smenge l st leer"); return; } rs. fi rst(); System. out println(); System. out. pri nt 1n( rs. getRow() + " " + rs. getStri ng (l) + " " + rs .ge tString(2) + " " + rs.getString(3) 2.7 Navigation und Änderungen in der Ergebnismenge + " " + rs .getString(4)); System.out.println (); System.out println (" Ihre Ei ngabe bitte; "); System.out.println("<Bestand>, insert <ISBN>, del ete, + "nur RETURN oder quit"); BufferedReader br ~ new BufferedReader( new Input St r eamReade r (Sys t em. i n ) ) ; String line; wh ile ((line ~ br.readLine()) i f (line equals("q uit")) br eak; !~ null) { try { if (line equals("")) { i f (rs. next () ) System.out.println(rs getRow() + " " + rs.getString(l) + " " + rs.getString(2) + " " + rs.getString (3) + " " + rs.getString(4)); else ( System.out pr intln("ENDE"); break; } else if (line.startsWith("insert ")) ( String isbn ~ line.substring(7).tr im(); rS.moveTo InsertRow(); rs. updateStri ng( 1. isbn); rs. i nser tRow(); rs .moveToCurrentRow(); System. out. pri nt 1n (" insert OK"); el s e i f (line.equals("del ete")) rs .de1eteRow () ; System.out.println("del ete OK"); rs .previous(); el s e { try ( int bestand ~ Int ege r . par s el nt (l i ne ) ; rs.update lnt(3. bestand); rs.updateTimestamp(4. new Timestamp(System .currentTi me Hi 11 i s ())) ; rs. updateRow(); System.out.println(rs.getRow() + " " + rs.getString(l) + " " + rs.getString(2) + " "+ rs.getStr ing(3)); catch (NumberFormatException e) ( System. err. pri nt 1n ( "Keine Int ege r z ahl " ) ; 73 2 Datenbankanwendungen mit ]DBC 74 catch (SQL Exception e) System.err.println( e); } catch (Exception e) ( System. err println(e); finally { t ry { if (rs !~ null) rs.close(); if (stmt !~ nul l ) stmt.close(); if (con !~ null) con.close() ; catch (SQL Exception e ) ( 2.8 CachedRowSet - eine verbindungslose Ergebnismenge Ein Resu ltSet bietet den Zugriff auf das Abfrageergebnis mit Hilfe von internen Zeigern. Allerdings handelt es sich hierbei nicht um einen Container, der Daten speichert. Spätestens mit dem Schließen der Datenbankverbindung sind alle Daten verloren. Implementierungen des Interfaces j avax. s q1 . RowSet speichern die Zeilen des Abfrageergebnisses und ermöglichen auch die Navigation und Änderungen. RowSet ist ein Subinterface von ResultSet. CachedRowSet Wir behandeln hier ein Subinterface von RowSet: j avax. sq1. rowset. CachedRowSet Seit Java SE 5 wird eine Implementierung dieses Interfaces mit ausgeliefert: com.sun.rowset.CachedRowSet Impl CachedRowSet Imp l-Instanzen sind serialisierbar. Die CachedRowSet-Methode voi d populate(Resul tSet rs) t hr ows SQLException übernimmt das übergebene Abfrageergebnis rs. 2.8 CachedRowSet - eine verbindungslose Ergebnismenge 75 Ohne eine Datenbankverbindung können die Daten mit den entsprechenden Resu ltSet-Methoden geändert werden (siehe Kapitel 2.7). Diese Änderungen können dann später in der Datenbank nachvollzogen werden. Hierzu dient die folgende Methode, vOld acceptChanges(Connectlon con) throws SyncProvlderExceptlon javax. sq l . rowset. spi .SyncPro vi derExcepti on ist Subklasse von SQLException. Ein CachedRowSet eignet sich vor allem in verteilten Umgebungen. Daten können z. B. auf ein mobiles Endgerät übertragen werden und dort ohne Datenbankverbindung geändert werden. Später kann eine Übertragung zurück zur Datenbank erfolgen und die Daten können eingespielt werden. Folgendes Szenario soll die Verwendungsmöglichkeit demonst- Programm2.14 rieren: 1. Buchdaten werden abgefragt und in einem CachedRowSet ge- speichert. Anschließend wird dieses serialisiert (RowSetTestl). 2. Das CachedRowSet wird rekonstruiert (Deserialisierung) und der erste Datensatz wird geändert. Die Instanz wird wiederum serialisiert (RowSetTest2). 3. Das CachedRowSet wird rekonstruiert (Deserialisierung) und die Änderungen in der Datenbank eingespielt (RowSetTest3). Bild2.11.· Cached RowSet Testszenario OB buch.dat CachedRowSet CachedRowSet ® Update Zur Vereinfachung der Prozesse dient die Klasse RowSetUti 1i ty. Sie enthält den Konstruktor RowSetUtility(String url. String user. String passwordl zur übergabe der Verbindungsparameter, die Methode boolean executeQuery(String query) 76 2 Datenbankanwendungen mit ]DBC zur Erzeugung des Abfrageergebnisses mit Speicherung in einem CachedRowSet-Objekt, die Methode boolean propagate() zur übernahme der Änderungen in die Datenbank, die get/setMethoden CachedRowSet get RowSe t() und void setRowSet(CachedRowSet rowSet) sowie die Methoden zur Serialisierung und Deserialisierung statlc vOld serlallze(CachedRowSet crs, Str lng fll ename) throws IOExcept l on und static CachedRowSet deser ialize(String fil ename) throws IOExcept i on, ClassNot FoundException Row5etUtility lmport lmport lmport lmport lmport lmport lmport import import ja va ja va java java ja va ja va ja va ja va ja va lo. Fll eInputStream; lo. Fll eOutputStream: lo. IO Exceptlon; lO ObjectInputStream; lO.ObjectüutputStream; sql .Connectlon; sql.DrlverManager; sql.ResultSet; sql.SOL Exception; lmport ja va sql.Statement; lmport ja vax.sql .rowset.CachedRowSet; lmport com.sun.rowset.CachedRowSet Impl; public class RowSetUtility ( prlvate CachedRowSet rowSet; pr i vat e Strl ng ur 1 ; prlv at e Strlng user; prlvate Strlng password; public RowSetUtility(String ur}, String user. String password) ( this.url -vurl : thls.user = user; thlS password = password; public boolean executeOuery(String query) { Connect l on con = null; Statement stmt ~ null; ResultSet rs ~ null; 2.8 CachedRowSet - eine verbindungslose Ergebnismenge try ( con = DrlverManager.getConnectlon(url, user, password); stmt = con.createStatement(); rs = stmt. executeQuery(query); rowSet ~ new CachedRowSetlmpl(); rowSet.popu1ate( rs); return true; catch (SQLException e) return false; finally { try { if (rs !~ null) rs .close(); if (stmt !~ null) stmt. cl ose () ; if (con !~ null) con.close(); catch (SQLException e) public void setRowSet(CachedRowSet rowSet) ( this.rowSet = rowSet; public CachedRowSet getRowSet() return rowSet; public boolean propagate() Connection con = null; try ( con = DriverManager.getConnection(url, user, password); con.setAutoCommit(false); rowSet.acceptChanges(con); re turn true; catch (SQLException e) return false; finally { try { if (con !~ null) con.close(); catch (SQLException e) 77 2 Datenbankanwendungen mit ]DBC 78 publlC statlc vOl d serlallze (CachedRowSet crs, String filename) throws IOExcept i on ( ObjectOutputStream out ~ new ObjectOutputStream( new Fi le Out put St ream(fi l ename )) ; out writeObject(crs); out fl ush() ; out close(); public stat ic CachedRowSet deserialize(String filename) throws IOExcept i on, ClassNotFound Except ion ( ObjectlnputStream in ~ new ObjectlnputStream( new Fi l el nput St ream(f i l ename) ) ; CachedRowSet crs ~ (CachedRowSet) in,readObj ect(); in,clos e(); r eturn crs; RowSetTestl lmport java l o.Fl l eI nput St ream; lmport java utll.Propertles; l mport javax.sql .rowset.CachedRowSet; public class RowSetTest l ( public static voi d main(String[] args) { Fi lel nput St re am in; try ( j n = new Fl l eI nput St re am( "dbconnect propertles" ); Propertles prop = new Propertl es(); prcp. load(in); in,close(); Stri ng ur1 ~ prcp .getProperty(" ur1"); String user ~ prop,getProperty("user", ""); String password ~ prop,getProperty("password", ""); RowSetUtility util password) ; ~ new RowSetUtility(url, user, = "select i sbr , t i tel . bestand, stand" "from buch order by isbn"; i f (uti 1, executeOuery(query)) ( CachedRowSet crs ~ util ,getRowSet(); Strlng query while (crs,next()) ( System,out,println( crs getRow() +" " + crs,getString(l) + " " + crs getString(2) + " " + c rs .get St r l ng (3) + " " + crs,getString(4)); + 2.8 CachedRowSet - eine verbindungslose Ergebnismenge 79 RowSetUti 1i ty. seri al i ze(crs. "buch.dat") ; } catch (Exception e) ( System.err.println(e); lmport java.sql .Tl mestamp; RowSetTest2 lmport javax.sql.rowset.CachedRowSet; public class RowSetTest2 ( public static void main(String[] args) { try ( CachedRowSet crs ~ RowSetUtility.deserialize( "buch.dat") ; crs.absolute(I); int bestand ~ crs.getlnt(3) + 10; crs.updatelnt(3. bestand); crs.updateTl mestamp(4, new Tlmestamp(System .currentTi rreMi 11 i s ())) ; crs. updateRow(); RowSetUti 1i ty. seri al i ze(crs. "buch.dat") ; catch (Exception e) ( System.err.println(e); lmport java.lo.FlleInputStream; lmport java.utll.Propertles; lmport javax.sql.rowset.CachedRowSet; public class RowSetTest3 ( public static void main(String[] args) { FilelnputStream in; try { tn = new FII er nputSt ream ("dbconnect propertl es" ) ; Propertles prop = new Propertles(); prop.load(in) ; in.close() ; String url ~ prop.getProperty("url"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); RowSetTest3 2 Datenbankanwendungen mit ]DBC 80 RowSetUtility util password) ; ~ CachedRowSet crs RowSetUtility,deserialize( ~ new RowSetUtility(url, user, "buch.dat"»: uti 1, setRowSet(crs); i f (uti 1, propagate()) ( System,out,println("OB-Update erfolgreich"); else ( Systeri.out println("Fehler bei OB-Update"); } catch (Exception e) ( System,err,println(e); 2.9 Exkurs: XML-Dokumente aus SOL-Abfragen erzeugen Thema dieses Kapitels ist die Entwicklung eines Programms, das das Ergebnis einer beliebigen SQL-Abfrage in eine XML-Struktur überführt und diese dann in ein anderes Format (z. B, HTML) mittels XSLT (Extensible Stylesheet Language Transformation) transformiert. XMI Zunächst einige Bemerkungen zu XML: Mit so genannten Auszeichnungssprachen (wie z. B, HTML) können beliebigen Textelementen Eigenschaften zugewiesen werden, wodurch deren Formatierung oder Bedeutung festgelegt wird, Die Textelemente werden durch Markierungen (tags) gekennzeichnet. Auf eine Startmarkierung folgt das Textelement, das in der Regel durch eine Endemarkierung abgeschlossen wird, Dabei sind die Markierungen im Allgemeinen geschachtelt, d.h. im markierten Text können weitere Markierungen enthalten sein. Das ermöglicht eine hierarchische Strukturierung des Inhalts, XML (Extensible Markup Language) ermöglicht mit selbst definierten Tags eine genaue Beschreibung strukturierter Informationen, die dann maschinell ausgewertet werden können. Im Unterschied zur Sprache HTML, deren Tags fest vorgegeben sind, ist XML eine Metasprache, mit der anwendungsspezifische Auszeichnungssprachen definiert werden können. 2.9 Exkurs, XML-Dokumente aus SQL-Abfragen erzeugen 81 Das XML-Dokument param.xml enthält Angaben zur Herstellung Programm 2.15 einer Datenbankverbindung, eine SQL-Abfrage und einige Dateinamen. Der allgemeine Aufbau dieser Datei sieht wie folgt aus: <params> <param> <param name>Parametername</param-name> <param value>Parameterwert</param- value> </param> </params> Das zu entwickelnde Programm 2.15 (Db2Xm1) liest diese Parameter mit Hilfe der Klasse XMLPararreters zu Beginn ein. <?xml verslon="1.0" encodlng=" ISO 8859- 1"?> <params> <!- DB-Verblndung --> <param> <param name>url</param-name> <param value>jdbc:mysql ://localhost/buecher</param-value> </param> <param> <param name>user</param-name> <param value>root</param-value> </pararn> <param> <param name>password</param-name> <param value>root</param-value> </param> SQL-Abfrage --> <param> <param-name>sql</param name> <param-val ue> select autor, tl tel, lsbn, prels, bestand, stand from buch order by autor. titel </param-value> </param> <!- <!- XML-Ausgabe --> <param> <param name>xmlFlle</param -name> <param value>result/result.xml</param val ue> </param> param.xml 2 Datenbankanwendungen mit ]DBC 82 <!- XSLT-Template --> <param> <param name>xs lFlle</param-name> <param value>template.xsl</param value> </param> <!- Output --> <param> <param name>outputFlle</param-name> <param value>result/result.html</param value> </param> </params> Die Leistung der Klasse XMLParameters besteht im Parsen des XML-Dokuments mit Hilfe eines SAX-Parsers (Simple API for XML) und der Bereitstellung der Parameternamen und -werte in Form eines Propertl es-Objekts. Ein SAX-Parser bietet ein ereignisorientiertes API. Der Parser liest das XML-Dokument sequentiell durch und ruft spezielle Callback-Methoden immer dann auf, wenn er im Eingabestrom ein Tag oder ein Textelement entdeckt. XMLParameters package xmlutils; l mport l mport l mport l mport java. i c. FileNotFoundException; java. i c. FileReader; java. i c. IOExceptlon; java .uti l . Propertl es; lmport javax.xml .parsers.ParserConflguratlonExceptlon; lmport javax.xml .parsers.SAXParser; lmport javax.xml .parsers.SAXParserFactory; l mport l mpor t l mpor t l mpor t l mpor t org. xml org. xml org. xml org. xml org. xml .sax. Attrl butes; .sax. Input Source ; .sax. SAXException; .sax. XMLReader; .sax. helpers.DefaultHandler; public class XMLParameters extends DefaultHandler prlvate Strlng element; pr i vate Strl ng narre = " " : pr i vate Strl ng val ue = " " ; prlvate Propertles propertles = new Propertles(); public XMLParameters(String filename) throws FileNotFoundException. IOException. ParserConflguratlonExceptlon, SAXExceptlon 2.9 Exkurs, XML-Dokumente aus SQL-Abfragen erzeugen SAXParserFaetory f aet ory ~ SAXParser Faetory.newlnstanee(); SAXParser parser ~ faetory.newSAXParser(); XMLReader reader ~ parser.getXMLReader(); reader.setContentHandler (this); reader.parse(new Input Souree (new Fi l eReader (f i l ename ) ) ) ; publie voi d startElement(String namespaeeURI. String loealName. String qName. Attributes attrs) throws SAXExeeption el ement = qName ; publie void eharaeters(ehar[] eh. int start. int length) throws SAXExeeption // Textlnhalte können slch über me hrere Zell en ers trecken. String text ~ new String (eh. start. length).trim(); i f (text.length() ~~ 0) return ; if (element.equals("param-name")) if (name.length() > 0) name += " ": name += t ext ; else if (element.equals("param val ue") ) ( if (value.l ength() > 0) value += " '": value += text; publie void endEl ement(String namespaeeURI. String loealName. String qName) throws SAXExeeption { if (qName.equals("param")) ( propertles.setProperty (name, value); name = uo. val ue = ""; publie Properties getParameters() return propertles; 83 2 Datenbankanwendungen mit ]DBC 84 XML Pararreters ist von der Klasse l:ef aultH andl er abgeleitet, die Default-Implementierungen für alle Callback-Methoden enthält Die Callback-Methoden startElerrent, endEl ement und characters werden in der Subklasse XMLParameters überschrieben. Im Konstruktor von XMLPar amet er s wird ein SAXParserFactoryObjekt erzeugt, mit dessen Hilfe dann der eigentliche Parser erzeugt wird, Die Methode parse des Interface XMLReader liest das XML-Dokument und ruft Callback-Methoden auf Die Callback-Methoden in XMLPar amet er s sind so implementiert, dass führende und nachfolgende Leerzeichen beim Parameternamen und -wert ignoriert werden . Insbesondere kann sich der Parameterwert über mehrere Zeilen erstrecken. Die Methode con vert der Klasse XMLCon verter erzeugt aus dem Ergebnis einer SQL-Abfrage (ResultSet) ein XML-Dokument der Form: <resu lt> <headers> <header>Spaltennamel</header> </headers> <rows> <row> <Spaltennamel>Wertl</Spa ltennamel> </row> </rows> </result> Die kritischen Zeichen &, <; >, ' und" werden durch so genannte Entity-Rejerenzen ersetzt. XMLConverter package xmlutils; import ja va sql.ResultSet; import ja va sql.ResultSetM2taData; import ja va sql .SQL Exception; publ ic class XMLConverter prlvate ResultSet rs; publ i c XMLCon verter(Res ultSet rs) { thls.rs = rs; 2.9 Exkurs, XML-Dokumente aus SQL-Abfragen erzeugen public String conver t() throws SQLException ( StringBuilder sb ~ new StringBuilder(); String sep ~ System.getProperty("line.separator"); sb append( "<?xml versiowl"1.01" encoding~I"ISO 8859-11"?>" + sep); sb append( "<resul t>" + sep); sb append("lt<headers>" + sep); ResultSetMetaOata rs md ~ rS.getMetaOata(); int n ~ rs md.getColumnCount(); for (int i ~ 1; i <~ n: i++) ( sb.append("ltlt<header>" + rsmd getColumnLabel (i) + "</header>" + sep); sb append( "I t</headers>" + sep); sb append("lt<rows>" + sep); while (rs.next()) ( sb.append("ltlt<row>" + sep); for (int i = 1; i <= n; i++) sb.append("ltltlt<" + rs md.getColumnLabel(i) + ">" + rep1ace (rs .getSt ri ng (i )) + "</" + rs md.getCo 1umnLabe l( i) + ">" + sep); } sb append("ltlt</row>" + sep); sb append ( "I t</ rows>" + sep); sb append("</result>"); return sb.toString(); private String replace(String value) ( StringBuilder sb ~ new StringBuilder(); in t n ~ value.length(); for (int i = 0; i < r: i++) char c ~ value.charAt(i); switch (c) ( case '&': sb.append("&amp; "); break; case '<": sb. append( "& l t;"): break; case '>": sb. append( "&gt;"); break; 85 86 2 Datenbankanwendungen mit ]DBC case '\": sb.append("&apos;"); break; case'\"': sb. append("&quot;"); break; defau l t: sb. append(c); return sb.toString(); Die Methode trans form der Klasse XML Transforrrer wandelt ein XML-Dokument in eine andere Struktur (z. B. ein HTMLDokument) mit Hilfe eines Stylesheets um. Die Extensible Stylesbeet Language Transformation (XSLT) beschreibt, wie XMLDokumente in andere Formate transformiert werden können. In unserem Beispiel wird das Stylesheet template.xsl verwendet. template.xsl <?xml ver s l on="l . O" encoding="ISO-8859-1"?> <xsl : styl esheet ver s i on-" 1.0" xm1ns ;xs1~"http ;/ /www .w3. org/1ggg /XSL/T rans form"> <xsl ;output method~"htm1" encoding~"ISO-8859-1" /> <xsl :template match="result"> <html> <head> <tit1e>SOL Ergebnis</tit1e> </head> <body> <tab1e border-"}" cel lpadd im-"b"> <tr bgco1or~"lightGrey"> <xsl ;app1y-temp1ates se1ect~"headers/header" /> </tr> <xsl :apply-templates select="rows/row" /> <!tab 1e> </body> </htm1> </xsl :templ ate> <xs l : templ ate match=" header"> <th a1igw"left"> <font face="Ari al "> <xsl:value-of select="." /> </font> </th> </xsl :templ ate> <xsl :template match="row"> <tr> <xs1 ;app1y-temp1ates se1ect~"*" /> 2.9 Exkurs, XML-Dokumente aus SQL-Abfragen erzeugen 87 </ t r > </ xs 1: temp 1ate> <xs 1: temp1ate match-">"> <td> <fant face="Arlal"> <xsl :value-of select="." /> </ fant> </td> </ xs 1: temp 1ate> </xsl :s tylesheet> template-Elemente legen fest, wie die Ausgabe aussehen soll. Ihr Inhalt definiert eine Vorlage, die mit den Daten des XMLDokuments gefüllt wird. Das Attribut match des Tags xs1 :t emp1ate gibt an, für welches Element des XML-Dokuments diese Vorlage gilt. package xmlutils: lmport java.lo Reader; lmport java.lo StrlngWrlter; lmport l mport lmport lmport lmport lmport javax.xml.transform.Transformer; j avax. xm 1 . trans form. Trans formerConfl gurat l onExceptl on; javax.xml.transform.TransformerExceptlon; javax.xml.transform.TransformerFactory; javax.xml.transform.stream.StreamResult; javax.xml.transform.stream.StreamSo urce; public class XMLTr ans f orme r ( prlvate Transfor mer transformer; public XMLTransformer(Reader xsl lnputl throws TransformerConflQuratlonExceptlon TransformerFactory factory = TransformerFactory .new Instance () ; transformer = factory.newTransformer(new StreamSource( xslInputll: public Str ing trans form(Read er xml lnputl throws TransformerExceptlon { StrlngWrlter out = new StrlngWrlter(); transformer.transform(new StreamSource(xml Input ) , new StreamResult(outll: return out.toString(): XMLTransfonner 88 2 Dat enbankan wendungen mit JDBe Im Konstruktor von XMl Tr ansf ormer wird ein TransformerFacto ryO bjekt erzeugt , mi t dessen Hilfe dann der eigentliche Transfor mer auf der Basis eines Sryle sheets erzeugt wird Die Methode t ransfo rm der Klasse Transformer wand elt d as XML-Dokument um. Die ma i n-Methode der Kla sse Db2Xm 1 führt di e folgende n Schri tte aus: • Einle sen der Paramet er au s param.xml mit Hilfe von XML - Paramete rs • Aufb au der Datenbankverbindung • Au sfüh rung der &2L-Abfrage • Erzeug ung des XML-Dokumenrs mit Hilfe von X""-.Con ver ter • XSL-Transfo rma tion auf Basis de s Srylesheers ternplate .xsl mit Hilfe von XMLTr ansformer • Au sgabe in reeultbtml Bild 2 .12: Progra mma blauf Tabelle buch !iiot... paramxml 1_ 3-15-00130&-9 )-15-001562-6 3-15-010606-0 3-257-20996-3 3-257-2103+ 5 3-257-21166->< Iltel 0 ,,:l<e"5. Chllrles D,,:l<en5.Chllrles Dickens. thones Dickens, cn"rles Dickens, cn " rles Dicke ns, Charles Schwere le~en GroBe Erw" rtunge n Oe, We.hn"chls"bend Nikol"s Nickleby D""; d Coppertield Bleakha us <p a~-" <p a~ano-n_ >s ql < /p arano- n_> <parano-value> s elKt ••• frOlll • • • </ pa r ano-v a l ue> </ pa r Vl> WhH ~ Converter t emplat e.xsi <?xml v eU10n- "1. 0 " e nc OCl 1nlJ- " I SO 8859 1 " ?> <xe l :e t y lu he et veU 10 n- " 1 . 0 " x ml n ~ : x~l - . <xs l : o ut put ""'t hoCl- " ht ml " ~ncodi nlJ - . I> <xs l : t emp l ate _ lO ch- "~ ~ ~u lt "> <7 xml v e u 1o n-" 1 . 0" e nc ocll n\l- " I SO- BB59- 1 " ?> < ~ ,,~ ult > <l>"" d"u > </ l>" " d"u> <r "ws> <I xs 1 : temp l at e > <lxsl : styles heet> </r" w> <h e s u lt> Transformer rnullhtml autor tite l [ Dc eens. Charles i BleakHause ~ens. cnenes : Bleakhaus [ DlCkens. Charles I Das Geheimris desEdwin Drood [DICkeM. cneoes I Davld Cop perfield 2.9 Exkurs, XML-Dokumente aus SQL-Abfragen erzeugen lmport import import import import import import import java. ia Fi l eReader ; java. la Fi l eWri t er ; java. la PrintWriter; java. io.StringReader; java sql.Connection; java sql.Dri verManager; java sql.ResultSet; java sql.Statement; import java.utll. Properties; import xmlutils. XMLConverter; import xmlutils.XMLParamet ers; import xmlutils.XMLTransformer; public class Ob2Xml ( public static void main(String[] args) { Properti es prop ~ null; Connection con = null; PrintWriter xmlOut ~ null; Pri nt WrHer out = null ; try ( // Parameter ein lesen XMLParameters params prop = = new XMLPararreters("param.xml"); params.getParameters(); String url ~ prop.getProperty("url"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); // Verbindung zur OB herstellen con = Dri verManager.getConnection(url, user, password) ; // SOL-Abfrage ausführen Statement stmt = con.createStatement(); String sql ~ prop.getProperty("sql"); if (sql ~~ null) t hrow new Except i on( "Par amet er I"sqll" fehlt"); ResultSet rs ~ s tmt.executeOuery(sql); // XML-Ausgabe erzeugen XMLConverter conv = new XMLConverter(rs); Strlng xml = conv.convert(); rs .close(); stmt. cl ose () ; String xml File ~ prop.getProperty("xml File"); if (xml File !~ null ) ( xmlOut ~ new PrintWriter(new Fi l eW r i t er (xml Fi l e ) ) ; xmlOut.print(xml ); 89 Db2Xmi 2 Datenbankanwendungen mit ]DBC 90 xmlOut. fl ush(); System.out.println("XML-Ausgabe; " + xmlFile); // XSL-Transformation String xslFile ~ prop getProperty("xs lFile"); i f (xsl Fi le ~~ null) throw new Exception("ParafTl2ter \"xs lFile\" fehlt"); XMLTransformer trans = new XMLTransformer( new FileReader(xslFile)); String output ~ trans.transform(new StringReader(xml)); Stri ng outputFi 1e ~ prop. getProperty( "outputFi 1e"); if (outputFile ~~ null) throw new Exception("ParafTl2ter \"outputFile\" fehlt"); out ~ new PrintWriter(new FileWriter(outputFile)); out .print(output) ; out .flush(); System.out.println("Ausgabe der XSL-Transformation; " + outputFi 1e); catch (Exception e) ( System.err.println(e); finally { try { if (con !~ null) con.close() ; if (xmlOut !~ null) xmlOut.close(); i f (out !~ null) out.close() ; catch (Exception e) ( System.err.println(e); XMI-Dokument result.xml <?xml verslon="l.O" encodlng="ISO-8859-1"?> <resu lt> <headers> <header>autor</header> <header>tltel</header> <header>lsbn</header> <header>prels</header> <header>bestand</header> <header>stand</header> </headers> 2.10 Exkurs, Stored Procedures 91 <rows> <row> <autor>Dickens, Charles</autor> <titel>Bleak House</ t i t el > <isbn>3- 458-32810 -6</isbn> <preis >17</preis> <bestand>16</bestand> <stand>2010-02-07 OO:OO:OO.O</stand> </row> </ rows> </result> 2.10 Exkurs: Stored Procedures Stored Procedures (gespeicherte Prozeduren) bestehen aus SQLAnweisungen und zusätzlichen Befehlen zur Deklaration von Variablen, zur Bildung von Schleifen, Verzweigungen usw. Solche Prozeduren ermöglichen es , einen Teil der Logik vom Vorteile Clientprogramm auf den Datenbankserver zu verlagern, was oft zu einer Performanceverbesserung führt, da die Prozedur komplett auf dem Datenbankserver ausgeführt wird und keine Übertragung von Zwischenergebnissen zum Client erfolgen muss. Durch die Auslagerung zentraler Codeteile auf den Datenbankserver können Coderedundanzen in den Anwendungsprogrammen vermieden werden, was die Anwendungen besser wartbar macht. Beispielsweise muss bei Änderung des Datenmodells in der Datenbank nur eine Stored Procedure geändert werden, nicht aber der Code in allen betroffenen Anwendungsprogrammen. Leider ist die Definition von Stored Procedures abhängig vom Nachteile jeweiligen Datenbankmanagementsystem. Fast jedes System nutzt eine eigene Syntax und zusätzliche Spracherweiterungen, was eine Portierung auf ein anderes System sehr aufwändig machen kann. In den folgenden Beispielen nutzen wir das DBMS MySQL, das ab Version 5.0 Stored Procedures unterstützt. Das Interface ja va. sq l .CaII abl eStaterrent, ein Subinterface von CallableStatement java.sql.PreparedStaterrent, wird zum Aufruf von Stored Procedures eingesetzt. Im ersten Beispiel nutzen wir die Stored Procedure zeige- Programm 2.16 Buecher, die alle Bücher aus der Tabelle huch (siehe Kapitel 2.2.1) liefert, deren Erscheinungsjahr größer oder gleich einem als Parameter vorgegebenen Jahr ist. 2 Datenbankanwendungen mit ]DBC 92 Prozedur zeigeBuecher del imiter $$ create procedure zelgeBuecher (IN j lnt) begin select isbn, autor, titel fram buch where jahr end $$ >= j; de1untcr Die Prozedur hat einen mit I N gekennzeichneten Eingabeparameter. Da der Code selbst das Zeichen ; enthält, ist dieses als Delimiter-Zeichen zum Abschließen von SQL-Anweisungen nicht geeignet und wird deshalb temporär in das Zeichen $$ geändert Die Prozedur kann beispielsweise mit dem Kommandointerpreter mysql erstellt werden, mysql -u root -p buecher ZeigeBuecher < zeigeBuecher.sql Die folgende ]DBC-Anwendung ruft die Stored Procedure mit einem i nt-Parameter auf. import import import import import java java java java java io.FileInputStream; sql.CallableStaterrent; sql .Connection; sq 1. Ori verManager ; sql.ResultSet; import java util.Properties; public class ZeigeBuecher ( public static voi d main(String[] args) throws Exception ( int jahr ~ Integer.parse lnt(args[O]); FilelnputStream in ~ new FilelnputStream( "dbconnect.properties"); Properties prop = new Properties(); prop.load(in) ; in.close(); Stri ng ur1 ~ prop. getProperty(" ur1"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); Connection con password) ; = DrlverManager.getConnectlon(url, user, String str ~ "{call zeigeBuecher (?))"; CallableStatement stmt ~ con.prepareCall(str); stmt.setlnt(I. jahr); Resul tSet rs ~ stmt.executeQuery(); 2.10 Exkurs, Stored Procedures 93 while (rs.next()) ( System.out.println(rs getString(l) + " " + rS.getString(2) +" "+ rS.getString(3)); rs .close(); stmt. cl ose () ; con.close(); Die Connect l on-Methode CallableStatement prepareCall(String sql) throws SQLException erzeugt ein Ca 11 ab1eStatement-Objekt. Der Aufruf der Prozedur wird in Escape-Syntax notiert, wobei die Parameter jeweils durch ? festgelegt werden. So enthält sq1 im obigen Beispiel die Zeiehenkette. {call zeigeBuecher (?)}. Zur Belegung der IN-Parameter stehen alle setXxx-Methoden des Interface PreparedStatement zur Verfügung. Mit executeQuery wird die Ausführung der Prozedur gestartet und das Ergebnis der Abfrage zurückgeliefert. Die Eingabe "drop procedure ze i geBuecher" im Kommandointerpreter mysql entfernt die Prozedur. In einer Stored Procedure können Parameter zur Eingabe (IN), Programm2.17 zur Ausgabe (OUT) oder zur Ein- und Ausgabe (INOUT) benutzt werden. Dies zeigt das folgende Beispiel, dellmlter $$ Prozedur create procedure reservl ere IN _isbn varchar(!?). IN menge lnt, OUT reserVlert lnt, OUT fehl menge int reserviere begin declare lst lnt; select bestand fram buch where lsbn if menge <= ist then set reserviert = menge; set fehl menge ~ 0; = lsbn lnto lst; 94 2 Datenbankanwendungen mit ]DBC else set reservi ert = ist; set fehlmenge = menge end i f : ist; update buch set bestand ~ bestand reservi ert, isbn; stand = now() where isbn end $$ del imiter Die Prozedur vermindert den Bestand um die reservierte Menge. Die Anzahl nicht verfügbarer Exemplare wird als Fehlmenge ausgewiesen. Reserviere import ja va io. File InputStream; import ja va sql.CallableStaterrent; import ja va sql .Connection; import ja va sq l . Dri verManager ; import ja va sql. Types; import ja va util.Properties; publ ic class Reserviere { public stat ic void main(String[J args) throws Except i on ( Str ing isbn ~ args[OJ ; int menge ~ Int eger .parseI nt (args [l J ); Fi leI nput St re am in ~ new Fi leInput St re am( "dbconnect.properties" ); Properties prop = new Properties( ); prop.load(in) ; in.close(); Stri ng ur1 ~ prop. getProperty(" url"); String user ~ prop.getProperty("user". ""); String password ~ prop.getProperty("password". ""); Connection con password) ; = DriverManager.getConnection(url, user, String str ~ "{call reserviere (????l)"; CallableStaterrent stmt ~ con.prepareCall(str); stmt.setString( l. isbn); stmt.set Int(2. rre nge ) ; stmt.registerOutParameter(3. Types. INTEGER) ; stmt.registerOutParameter( 4. Types. INTEGER) ; stmt execute Update ( ) ; Syst em. out prin tln("Res ervi ert; " + stmt.ge tlnt(3 )); Syst em. out prin tln(" Fehlmenge; " + stmLgetlnt( 4) ); 2.11 Aufgaben 95 strnt. cl ose () ; con.ciose(); Ausgabeparameter (OUT) sowie Ein-/Ausgabeparameter CI NOUT) müssen registriert werden. Hierzu steht die folgende Ca 11 ab1eStatement-Methode zur Verfügung; void registerOutParameter(int idx. int sqiType) throws SQLException i dx bestimmt die Position des Parameters (die Zählung beginnt bei 1). sq l Type bestimmt den ]DBC-Typ (siehe Bild 2.8). Da die obige Prozedur eine Änderungsanweisung enthält, wird executeUpdate verwendet. Zum Auslesen der Ausgabeparameter werden dem ]DBC-Typ entsprechende getXxx-Methoden des Interface Ca 11 ab1eStatement benutzt. 2.11 1. Aufgaben Das Datenmodell in Kapitel 2.2.1 soll um Kunden. Bestellungen und Bestellpositionen ergänzt werden: Tabelle hunde. kunde i d knarre strasse piz ort lnteger varchar(30) varchar(20) varchar(5) varchar(20) Tabelle bestellung beste II l d kunde ld datum status siehe unten lnteger date lnteger Tabelle bestellposition. beste 11 i d isbn menge integer varchar(17) lnteger 96 2 Datenbankanwendungen mit JDBC Bild2.13· Erweitertes verlag 1 n buch Datenmodell 1 n 1 kunde n bestellung 1 n bestellposition Die bestellLd in der Tabelle bestellung soll mit Unterstützung des DBMS beim INSERT als laufende Nummer vergeben werden. Dazu gibt es bei den verschiedenen Systemen unterschiedliche Lösungen. Bei Access bewirkt der Datentyp counter für die Spalte bestelljid diesen Mechanismus. Bei MySQL kann in der CREATE-TABLE-Anweisung auto, increment genutzt werden: bestell_ld lnteger auto_lncrement Für Apacbe Derby sieht die entsprechende Anweisung so aus: bestell_ld lnteger generated always as ldentity a) Erzeugen Sie eine Datei, die hintereinander alle SQLAnweisungen zum Aufbau der Tabellen (create tab 1e) und zum Einfügen einiger Datensätze in die Tabelle kunde (i nsert) enthält. Jede einzelne SQL-Anweisung soll mit";" abgeschlossen werden. b) Schreiben Sie ein Programm, mit dem die SQL-Anweisungen aus der Datei in a) nacheinander ausgeführt werden können (Statement-Methode executeUpdate). Dabei sollen Leerzeilen und Zeilen, die mit "- -" beginnen, ignoriert werden. 2. Schreiben Sie ein Programm, das für einen Kunden Bestelldaten aufnimmt. Das Programm soll mit den Parametern kunde_id und datei aufgerufen werden. Die Kundennummer muss eine gültige ID aus TabeIle kunde sein. datei bezeichnet eine Datei, die z: B. folgenden Inhalt hat 3-15-001308-9 3-15-001562-6 3-15-010606-0 5 3 8 Pro Zeile sind die ISBN-Nummer des besteIlten Buches und die gewünschte Stückzahl enthalten. 2.11 97 Au fgaben Die Spalte status soll den Wert 0 erhalten. Die erzeuge Bestellnummer in der Tabelle bestellung muss in der Tab e lle bestellposition als Fre md sch lü sse l eingetragen werden. Für die einzelnen Date nba n ksy steme gibt es dazu unterschiedliche Verfa hre n: Bild 2.14 - bestellung ~ ~t e ll id , Ergebnisbeispie! kunde best ellposition b e5 t~ 1I id I ~_ bn , l- 15-001309· 9 1 l-1 5-001562· 6 1 3-15-010606-0 s a • Nach "mser-t i nto best ell ung "kann die letzte auto- Access matisch ge ne rie rte Nummer mit d er Anweisung "s el ect l!'0i dent i ty" abgefragt werden. D ie se wird dann in die Ta b elle bestellpositiors ein getragen. Wird an die Sta tement-Methode execute tocate als zweiter MYSQL und Pa rameter der Wert Statment,RETURN_G E ~ RATED_KEYS überge- ApacheDerby b en , so liefert die Statement-Methode getGenerated KeysO ein Resul ts et-Objekt , das die generierte Nummer enthält . 3. Be ste llte Bü ch e r müssen fü r d ie Au slie feru ng vorbereitet werden . Sch reiben Sie ein Progra mm , da s für eine vo rgegeb en e bestelUd folgende Aktionen durchfüh rt: a) Prü fen , cö die b e im Progra mma u fru f mitgegebene ID als beste/Ud in der Tab e lle bestellung vorkommt. We n n ja: b) Prü fen , ob diese Be ste llu ng noch u nb earbe itet (Status 0) ist. Wen n ja: c) Verfügba rkelt p rü fe n. Pro Beste llp ositio n muss ge p rüft werden, ob die gewünschte Stückzahl die Be sta n dsza hl aus der Tabelle buch n icht übersteigt . Entsp re che n de Me ld u nge n sind auszugeben. Wenn alle b este llte n Bü che r in der ge wü n scht e n Stückza hl v orrätig sin d: d) Lage rbe sta nd in der Tab e lle buch für a lle Beste llp o sition e n ge mä ß der bestellte n Stückzahl reduzieren und den Status in der Tab e lle bestellung auf den Wert 1 setzen. 98 2 Datenbankanwendungen mit ]DBC 4. Entwickeln Sie ein Programm, das die Bestandsdaten (isbn, bestand, stand) der Tabelle bucb der Bücher-Datenbank (z. B. verwaltet mit MS Access) in eine Datenbank eines anderen Datenbanksystems (z. B. My SQL) kopiert. Die hierfür geeignete Tabelle der Zieldatenbank soll ebenfalls mit diesem Programm erstellt werden. 5. Erstellen Sie eine Variante von Programm 2.6, die BatchUpdates (siehe Kapitel 2.4.2) benutzt.