Datenbankanwendungen mit JDBC 2.1 Die Architektur von JDBC

Werbung
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("& ");
break;
case
'<":
sb. append( "& l t;"):
break;
case
'>":
sb. append( ">");
break;
85
86
2
Datenbankanwendungen mit ]DBC
case '\":
sb.append("'");
break;
case'\"':
sb. append(""");
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.
Herunterladen