Virtual OS/2 International Consumer Education
VOICE Homepage: http://de.os2voice.org
September 2003

[Inhaltsverzeichnis]
[Vorherige Seite] [Nächste Seite]
[Artikelverzeichnis]

editor@os2voice.org


Datenbankzugriff mit Java, JDBC und JNDI

Von Per Johansson © September 2003, Übersetzung: Thomas Klein

Einleitung

Wie jeder Java-Programmierer weiß, ist es möglich, aus einer Anwendung heraus über JDBC auf eine relationale Datenbank zuzugreifen. Dieser Artikel beschreibt, wie dies auf dem modernen Weg mit DataSource und JNDI unter OS/2 gemacht wird unter Einsatz der momentan verfügbaren Software. Dieser Artikel richtet sich in erster Linie an Leser, die bereits über Java-Programmierkenntnisse verfügen oder mehr zu diesem Thema wissen möchten.

Die Beispieldatenbank enthält einige Angaben zu Artikeln aus dem VOICE-Newsletter und sollte vom Beispielprogramm wie folgt angezeigt werden:

Bildschirmfoto des Programmfensters
Abb. 1: Der Programmbildschirm

MySQL-Datenbank

Wir werden den MySQL Datenbankserver verwenden, obwohl wir aber auch jeden anderen Datenbankserver hätten einsetzen können, der über einen JDBC-Treiber verfügt, der unter OS/2 arbeitet. MySQL ist ein sehr beliebter und schlanker Datenbankserver. Gewöhnlich stellt er das Back-End für Anwendungen wie z.B. Webforen dar. Die OS/2-Version 3.23.50 ist erhältlich über Yuri Dario und von Hobbes.

MySQL kommt als WarpIN-Archiv. Standardmäßig wird als Zielverzeichnis der Installation \usr\local\mysql verwendet. Die Installation erzeugt einen Ordner auf Ihrer Arbeitsoberfläche. Öffnen Sie diesen und starten Sie "Start Server". Dann öffnen Sie das Objekt "Console". Wir werden nun einige SQL-Befehle verwenden, um eine simple Datenbank mit nur einer Tabelle anzulegen. Kopieren Sie einfach die folgenden Anweisungen und fügen Sie sie in das "Console"-Fenster ein.

create database jnditest;
use jnditest;
create table articles (title varchar(100), author varchar(50), issue varchar(20));
insert into articles values ("eComStation 1.1, the Install", "Mark Dodel, Christian Hennecke", "June 2003");
insert into articles values ("WarpVision - The Swiss Army Knife of Multimedia Players", "Mark Szkolnicki", "June 2003");
insert into articles values ("SCSI on OS/2 - A Personal View", "Per Johansson", "June 2003");
insert into articles values ("SBC Yahoo! DSL: It's My Turn Now", "Timothy F. Sipples", "June 2003");
grant all on jnditest.* to user@localhost identified by 'password';
select * from articles;

Die letzte Zeile dient lediglich zur Prüfung Ihrer Dateneingabe.

JDK

Zum Zeitpunkt der Artikelerstellung standen zwar bereits zwei Ausgaben des JDK 1.4.1 zur Verfügung, die nicht von IBM stammen, jedoch verwenden wir hier die letzte von IBM veröffentlichte Version 1.3.1. Diese ist erhältlich über IBM Software Choice oder für Benutzer von eComStation. Für unsere Zwecke müssen sowohl die  Laufzeitumgebung ("runtime") sowie das Toolkit installiert sein.

Weiter unten werden zwei verschiedene Versionen von Java genannt: J2SE (Java 2 Plattform, Standardausgabe) und J2EE (Java 2 Plattform, Enterprise-Ausgabe). Das IBM JDK scheint lediglich den Funktionsumfang der J2SE zu enthalten, obwohl die Dokumentation von IBM dazu keinerlei Aussagen trifft.

JDBC

JDBC (Java Database Connectivity) ist eine gemeinsame Schnittstelle für Datenbanken. Es wird lediglich ein Treiber für das verwendete Datenbanksystem sowie ein Treiber für die Plattform benötigt, auf dem es eingesetzt wird. Da in unserem Fall Java selbst die Plattform darstellt, verwenden wir einen MySQL-Treiber, der vom darunter liegenden Betriebssystem unabhängig ist.

JDBC wird im Artikel Accessing Databases Using Java and JDBC genügend beschrieben. Dieser Artikel wurde in der Juli-Ausgabe 1998 des EDM/2 (dem leider nicht mehr operativen Electronic Developer Magazine für OS/2) veröffentlicht, und obwohl er sich auf die MySQL-Datenbank bezieht, treffen alle darin gemachten Aussagen zu JDBC auch in unserem Kontext zu.

JDBC ist Bestandteil des J2SE und des IBM JDK 1.3.1., daher ist keine separate Installation nötig. Jedoch ist das DataSource Interface, welches wir verwenden wollen, erst mit JDBC Version 2.0 eingeführt worden (die javax.sql Klassen), das wiederum nur als Bestandteil von J2EE erhältlich ist (in Version 1.4.1, ist es auch in J2SE enthalten). Also müssen wir uns das zunächst noch von Sun besorgen.

Holen Sie sich das JDBC 2.0 Optional Package Binary (früher als JDBC Standard Extension Binary 2.0 bezeichnet) von Sun JDBC Technology. Der Dateiname lautet jdbc2_0-stdext.jar und datiert vom 1999-05-13. Aus Gründen der Vereinfachung sollten Sie es im Verzeichnis \java131\jre\lib\ext speichern, oder entsprechend dort, wo Sie Java installiert haben. Sie können es auch in einem beliebigen anderen Verzeichnis ablegen, allerdings müssen Sie dann dafür Sorge tragen, daß das verwendete Verzeichnis im classpath angegeben ist.

JDBC-Treiber für MySQL

MySQL Connector/J ist der offizielle JDBC-Treiber für MySQL und kann über die Webseite des Herstellers bezogen werden. Wir verwenden hier die letzte als stabil bestätigte  Version: mysql-connector-java-3.0.8-stable.zip mit dem Datum 2003-05-23.

Der Treiber wird installiert durch Entpacken des ZIP-Archivs in das Verzeichnis \mysql-connector-java\. Der für uns relevante Teil ist die Datei  mysql-connector-java-3.0.8-stable-bin.jar.  Sie können diese entweder in das Verzeichnis \java131\jre\lib\ext kopieren (siehe oben) oder das ursprüngliche Verzeichnis in den classpath eintragen. Letztere Methode habe ich im Rahmen dieser Demonstration verwendet.

Tatsächlich enthält dieses Paket zusätzlich auch das JDBC 2.0 Optional Package Binary (siehe oben), so daß man es eigentlich nicht separat herunterladen braucht, aber ich wollte hier darauf hinweisen, woher es stammt.

JNDI

Wenn Sie Material über JDBC-Programmierung lesen, besteht die Möglichkeit, daß Sie dabei auf Anweisungen stoßen werden wie

Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection(dbUrl);

die dazu dienen, die Treiberklassen mittels der DriverManager-Einrichtung zu laden. Dies ist die traditionelle Methode des Datenbankzugriffs, die wir nicht verwenden werden. Statt dessen verwenden wir das Objekt  DataSource , welches mit einem Dienst namens JNDI (Java Naming and Directory Interface) registriert wird. Die Gründe hierfür sind unter anderem:

Wenn Sie sich mit der ODBC-Verwaltung auskennen, werden Sie die Vorteile erkennen. Mehr dazu können Sie in Sun's The JNDI Tutorial und dem Artikel JNDI Overview in JavaWorld nachlesen.

JNDI ist bereits sowohl im J2SE als auch im IBM JDK 1.3.1 enthalten. Zusätzlich benötigen wir noch den Dateisystemdienst ("File System Service Provider"), 1.2 Beta 3 aus dem Sun Java Naming and Directory Interface. Der Dateiname lautet fscontext-1_2-beta3.zip und trägt das Datum 2000-06-27. Ich habe keine Ahnung, warum es sich hierbei immer noch um eine "Beta"-Version handelt.

Die relevanten Dateien sind fscontext.jar und die Utility-Bibliothek providerutil.jar. Entpacken Sie das Archiv nach \java131 oder entsprechend dorthin, wo  Sie Ihr JDK installiert haben. Die beiden Dateien landen zum Schluß in \java131\lib.

Das Programm

Nach all dieser Vorbereitungsarbeit schreiben wir nun eine kleine Anwendung, welche auf die Datenbank zugreift und deren Inhalt in einem Fenster ausgibt. Dieselbe Technik könnten wir auch verwenden, um ein Applet oder Servlet zu erstellen.

Grundgerüst

Legen Sie zunächst mit einem Texteditor eine Datei namens JndiTest.java an und geben Sie darin die folgenden Anweisungen ein:

public class JndiTest
{
public JndiTest()
{
}
public static void main(String[] args)
{
new JndiTest();
}
}

Kompilieren Sie das Ganze von einer Befehlszeile aus, indem Sie javac jnditest.java eingeben. So können Sie sehen, ob das Kompilieren funktioniert. Das erzeugt eine Klassendatei mit dem Namen JndiTest.class.

Lassen Sie diese Anwendung laufen, indem Sie java JndiTest eingeben. Zwar passiert zu diesem Zeitpunkt noch nichts, aber damit können Sie prüfen, ob die Anwendung lauffähig ist. Falls nicht, werden Sie eine oder mehrere Fehlermeldungen erhalten.

Grafische Benutzeroberfläche

Wir könnten nun einfach die aus der Datenbank gelesenen Einträge über den Konsolebildschirm ausgeben, aber wir möchten ein GUI (Graphical User Interface; eine grafische Benutzeroberfläche) erstellen, um das Ganze optisch etwas ansprechender zu gestalten. Da der Dreh- und Angelpunkt dieses Artikels der Datenbankzugriff ist, werde ich nicht weiter ins Detail gehen. Das GUI besteht aus einem JTable in einem JViewport, der Bestandteil eines JFrame ist. Die Tabelle enthält eine Instanz der Klasse ArticleTableModel, die dann als eine Schnittstelle zu einem Vektor fungiert, der wiederum Instanzen einer Article-Klasse enthält. Die article Instanzen enthalten zunächst nur Testwerte, damit wir prüfen können ob das Programm funktioniert. Kompilieren und starten Sie das Ganze wie oben beschrieben.

import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class JndiTest
{
/*
Der Vektor enthält alle article-Instanzen.
Wir verwenden einen Vektor (kein Array) damit wir in der Lage sind,
eine dynamische Vergrößerung in Abhängigkeit der Anzahl der
enthaltenen Datensätze in der Datenbank zu bewerkstelligen.
Die strings im Array werden als Überschriften für die Tabellenspalten verwendet.
*/
private Vector articles = new Vector();
private String[] fields = {"Author", "Title", "Issue"};

/*
Die Klasse enthält drei String-Attribute die beim Lesen eines
Datensatzes aus der Datenbank mit dessen Inhalten gefüllt werden.
Für jeden gelesenen Datensatz wird eine Instanz generiert.
*/

  private class Article
{
Article(String author, String title, String issue)
{
this.author = author;
this.title = title;
this.issue = issue;
}
private String author;
public String getAuthor()
{
return author;
}
private String title;
public String getTitle()
{
return title;
}
private String issue;
public String getIssue()
{
return issue;
}
}

/*
Für die Verwendung mit der Tabelle erzeugen wie eine eigene table model Klasse.
Sie dient als Schnittstelle zwischen dem Artikel-Vektor und dem Array aus Feld-Strings
*/

  private class ArticleTableModel extends AbstractTableModel
{
public int getColumnCount()
{
return 3;
}
public int getRowCount()
{
return articles.size();
}
public String getColumnName(int col)
{
try
{
return fields[col];
}
catch (IndexOutOfBoundsException ex)
{
return ("");
}
}
public Object getValueAt(int row, int col)
{
Article article ;
try
{
article = (Article) articles.get(row);
}
catch (IndexOutOfBoundsException ex)
{
return ("");
}
switch (col)
{
case 0 : return article.getAuthor();
case 1 : return article.getTitle();
case 2 : return article.getIssue();
default : return "";
}
}
};

/*
Diese Methode erzeugt lediglich die JFC-GUI-Komponenten.
*/
public Component createComponents()
{
JScrollPane pane = new JScrollPane();
ArticleTableModel articleTableModel = new ArticleTableModel();
JTable articleJTable = new JTable(articleTableModel);
pane.getViewport().add(articleJTable, null);
return pane;
}

/*
Vorerst werden nur manuell Testwerte erzeugt und im
Artikel-Vektor abgelegt.
*/
public void readDatabase()
{
articles.add(new Article("X", "Y", "Z"));
articles.add(new Article("XXX", "YYY", "ZZZ"));
}

/*
Die Hauptmethode erzeugt das GUI sowie einen 'listener'
(ein 'Fensterüberwacher'; Anm.d.Übers.) mit dessen Hilfe wir das
Programm kontrolliert beenden können und startet schließlich die
Methode, die die Datenbanksätze in unsere Tabelle lädt.
*/

  public static void main(String[] args)
{
JndiTest jndiTest = new JndiTest();
Component pane = jndiTest.createComponents();

    JFrame frame = new JFrame("JndiTest");
frame.getContentPane().add(pane, BorderLayout.CENTER);
frame.addWindowListener(
new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
}
);
frame.setSize(new Dimension(600, 450)); //The Golden Section :-)
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
frame.setLocation((screenSize.width - frameSize.width) / 2,
(screenSize.height - frameSize.height) / 2);
jndiTest.readDatabase();
frame.setVisible(true);
}
}

Datenbank

Jetzt kommen wir zum vollständigen Programm. Wir stellen eine Verbindung zur Datenbank her, lesen alles in unseren articles Vektor und zeigen es an in articleJTable. Die Anzahl und Namen der Tabellenspalten sind zwar im Programm fest "verdrahtet", aber zumindest wird die Anzahl der Datensätze in der Datenbank (in unserem Fall 4) vom Programm korrekt ermittelt und angezeigt.

Um eine bessere Konzentration auf die datenbankrelevanten Teile zu erreichen,. habe ich im obigen Beispiel die Kommentare ausgelassen.

import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;

public class JndiTest
{
private Vector articles = new Vector();
private String[] fields = {"Author", "Title", "Issue"};

  private class Article
{
Article(String author, String title, String issue)
{
this.author = author;
this.title = title;
this.issue = issue;
}
private String author;
public String getAuthor()
{
return author;
}
private String title;
public String getTitle()
{
return title;
}
private String issue;
public String getIssue()
{
return issue;
}
}

  private class ArticleTableModel extends AbstractTableModel
{
public int getColumnCount()
{
return 3;
}
public int getRowCount()
{
return articles.size();
}
public String getColumnName(int col)
{
try
{
return fields[col];
}
catch (IndexOutOfBoundsException exception)
{
System.out.println(exception.getClass() + exception.getMessage() );
return ("");
}
}
public Object getValueAt(int row, int col)
{
Article article ;
try
{
article = (Article) articles.get(row);
}
catch (IndexOutOfBoundsException exception)
{
System.out.println(exception.getClass() + exception.getMessage() );
return ("");
}
switch (col)
{
case 0 : return article.getAuthor();
case 1 : return article.getTitle();
case 2 : return article.getIssue();
default : return "";
}
}
};

  public Component createComponents()
{
JScrollPane pane = new JScrollPane();
ArticleTableModel articleTableModel = new ArticleTableModel();
JTable articleJTable = new JTable(articleTableModel);
pane.getViewport().add(articleJTable, null);
return pane;
}

/*
Dies ist das SQL-Statement, das lediglich zur leichteren
Überarbeitung/Änderung in einzelne Zeilen zerlegt wurde.
*/
private String findStatement()
{
return
"select " +
"author ," +
"title ," +
"issue " +
"from " +
"articles " ;
}

/*
Verbindung und Datenquelle für den Datenbankzugriff
*/
private Connection connection;
private DataSource dataSource ;

/*
Wird aufgerufen aus der Hauptmethode des Programms.
Öffnet die Datenbankverbindung, liest die Inhalte in den
Artikel-Vektor und schließt die Verbindung wieder.
*/
public void readDatabase()
{
openDatabase("comp/env/jdbc/jnditest");
/*
Wir verwenden die Datenbankverbindung um aus der SQL-Anweisung in
findStatement ein PreparedStatement zu erzeugen.
Es wählt lediglich alle Felder aller Datensätze der Datenbank aus
und gibt Sie in ResultSet zurück.
Dann erzeugen wir in einer Schleife für jeden E
intrag aus ResultSet
eine
Article-Instanz erzeugt und im Article-Vektor abgelegt, von dem
das table model seine Dateninhalte bezieht.
*/
PreparedStatement preparedStatement ;
ResultSet resultSet ;
try
{
preparedStatement = connection.prepareStatement(findStatement());
resultSet = preparedStatement.executeQuery();
while (resultSet.next())
{
String author = resultSet.getString(1);
String title = resultSet.getString(2);
String issue = resultSet.getString(3);
articles.add(new Article(author, title, issue));
}
}
catch (SQLException exception)
{
System.out.println(exception.getClass() + exception.getMessage() );
return;
}
closeDatabase();
}

/*
Öffnet die Datenbank namens "comp/env/jdbc/jnditest". Die ersten drei
Bestandteile des Namens ergeben sich aus der JNDI-Spezifikation, während
der letzte unsere eigene Angabe darstellt.
*/
private void openDatabase(String databaseName)
{
/*
Registriert die DataSource aus dem Programm heraus in Ermangelung einer grafischen
JNDI-Verwaltungsoberfläche.
Dann der JNDI-bezogene Kram: Wir erzeugen einen InitialContext für den Dateisystemdienst
('File System Service Provider') und greifen auf die DataSource zu, indem wir den
Datenbanknamen als Parameter verwenden.
*/
try
{
registerDataSource(databaseName);
Context ctx = new InitialContext();
ctx.addToEnvironment(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
dataSource = (DataSource)ctx.lookup(databaseName);
}
catch(NamingException exception)
{
System.out.println(exception.getClass() + exception.getMessage() );
return;
}
/*
Wenn die DataSource haben, benutzen wir sie zur Herstellung des Datenbankzugriffs;
*/
try
{
connection = dataSource.getConnection();
}
catch(SQLException exception)
{
System.out.println(exception.getClass() + exception.getMessage() );
}
}

/*
Wir verwenden einfach die Verbindung, die mit openDatabase() hergestellt wurde
*/
private void closeDatabase()
{
try
{
connection.close();
}
catch(SQLException exception)
{
System.out.println(exception.getClass() + exception.getMessage() );
}
}

/*
Das folgende wurde abgeleitet aus dem Testcode zu MySQL-Connector/J. Der ursprüngliche
Autor verweist zwar darauf, daß man normalerweise nicht den Namen der Datenquelle im
Programmcode einbauen, sondern eher durch eine GUI zuweisen würde. Leider habe ich
keine solche GUI (grafische Oberfläche) finden können, warum wir es also dennoch im
Programm angeben. Es wird eine MysqlDataSource mit den benötigten Parametern erzeugt.
Dann wieder das JNDI-Zeugs: Wir erzeugen einen InitialContext für den File System Service Provider.
Schließlich binden wir noch im InitialContext den verwendeten Datenbanknamen ("jnditest") an die
MysqlDataSource.
Dies wird gespeichert in der Datei .bindings, die sich im Wurzelverzeichnis des Laufwerks
befindet, auf dem Java installiert wurde.
(Diese Datei ist eine reine Textdatei - Sie können die Inhalte also leicht anschauen.)
Dies muß nur einmal durchgeführt werden. Man könnte also den Code dafür aus dem Programm
entfernen, nachdem es einmal gelaufen ist. Oder man könnte es in eine separate Anwendung
auslagern.
*/
private void registerDataSource(String databaseName)
throws NamingException
{
com.mysql.jdbc.jdbc2.optional.MysqlDataSource dataSource = new com.mysql.jdbc.jdbc2.optional.MysqlDataSource();
dataSource.setServerName("localhost");
dataSource.setDatabaseName(databaseName.substring(databaseName.lastIndexOf("/") + 1));
dataSource.setUser("user");
dataSource.setPassword("password");
dataSource.setPortNumber(3306);
java.util.Hashtable env = new java.util.Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.fscontext.RefFSContextFactory");
Context ctx = new InitialContext(env);
ctx.rebind(databaseName, dataSource);
ctx.close();
}

  public static void main(String[] args)
{
JndiTest jndiTest = new JndiTest();
Component pane = jndiTest.createComponents();

    JFrame frame = new JFrame("JndiTest");
frame.getContentPane().add(pane, BorderLayout.CENTER);
frame.addWindowListener(
new WindowAdapter()
{
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
}
);
frame.setSize(new Dimension(600, 450)); //The Golden Section
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Dimension frameSize = frame.getSize();
frame.setLocation((screenSize.width - frameSize.width) / 2,
(screenSize.height - frameSize.height) / 2);
jndiTest.readDatabase();
frame.setVisible(true);
}

}

Um die benötigten Bibliotheken einzubeziehen, müssen die Befehle nun wie folgt verwendet werden:

Kompilieren:

javac -classpath F:\mysql-connector-java\mysql-connector-java-3.0.8-stable\mysql-connector-java-3.0.8-stable-bin.jar; jnditest.java

Programmausführung:

java -classpath F:\mysql-connector-java\mysql-connector-java-3.0.8-stable\mysql-connector-java-3.0.8-stable-bin.jar;F:\JAVA131\lib\fscontext.jar;F:\JAVA131\lib\providerutil.jar; JndiTest

Mehr zum Thema Java und relationale Datenbanken können Sie bei MartinFowler.com nachlesen. Einige der in meinen obigen Beispielen verwendeten Codeteile für den Datenbankzugriff stammen aus Beispielen aus seinem Buch "Patterns of Enterprise Application Architecture". Allerdings verwendet er dort nicht JNDI.

Sie können auch eine integrierte Entwicklungsumgebung ("Integrated Development Environment", IDE) wie z.B. Borland JBuilder anstelle des "nackten" JDK verwenden. Es könnte sein, daß darin Steuerelemente für Datenzugriffe enthalten sind, die sich dann um die Datenbankschnittstelle kümmern. Solche Steuerelemente habe ich zwar bereits bei der Arbeit mit Borland's VCL verwendet, allerdings noch nicht in Java.

Daten und Quellen:

In diesem Artikel referenzierte Verweise:
   MySQL - http://www.mysql.com/
   MySQL2 - http://www.ecomstation.it/yuri/mysql2/
   JDBC - http://java.sun.com/products/jdbc/
   EDM/2 Artikel zu JDBC - http://www.edm2.com/0607/msql3.html
   MySQL Connector/J - http://www.mysql.com/products/connector-j/
   DataSource - http://java.sun.com/j2se/1.4.2/docs/guide/jdbc/getstart/datasource.html
   Das JNDI Tutorial - http://java.sun.com/products/jndi/tutorial/index.html
   JNDI Übersicht in JavaWorld - http://www.javaworld.com/javaworld/jw-01-2000/jw-01-howto.html
   Sun Java Naming and Directory Interface - http://java.sun.com/products/jndi/
   MartinFowler.com - http://www.martinfowler.com/


Per Johansson ist Systementwickler in seiner Heimat Schweden. Sie können ihm im IRC im Subnetz #OS/2 und anderen Channels als Hawklord antreffen. Seine Homepage erreichen Sie unter http://per.johansson.name/ wo Sie noch mehr zu seinen Interessen Reisen, Medien und natürlich Computer herausfinden können.

[Artikelverzeichnis]
editor@os2voice.org
[Vorherige Seite] [Inhaltsverzeichnis] [Nächste Seite]
VOICE Homepage: http://de.os2voice.org