18.12 | JDBC und Portabilität |
Trotz der allgemeinen Definition einer relationalen Datenbankschnittstelle mit JDBC gibt es dennoch einige Punkte, die man bei der Programmierung von JDBC berücksichtigen sollte, wenn man wirklich portablen Code erstellen will, der auch auf anderen Datenbanksystem lauffähig ist:Obwohl die oben genannten Punkte eine geringere Portabilität von JDBC-Anwendungen mit sich bringen, kann man doch durch gutes objektorientiertes Design der Anwendung den Aufwand bei der Portierung auf ein anderes Datenbanksystem in Grenzen halten. Angenommen, es wird eine Anwendung für eine Oracle-Datenbank entwickelt. Dann können die Datenbankzugriffe in zwei Kategorien eingeteilt werden:
- Wenn ein JDBC-Treiber für ein bestimmtes Datenbanksystem verfügbar ist, heißt es noch lange nicht, dass er auch wirklich alle Features von JDBC unterstützt. Wenn das zugrunde liegende Datenbanksystem z. B. keine Transaktionen durchführen kann, ist das üblicherweise auch nicht über den JDBC-Treiber möglich. Bei der Benutzung eines Features, das der Treiber nicht unterstützt, wird normalerweise eine Exception ausgelöst. Das ist häufig bei kleineren Datenbanksystem wie z. B. MySQL der Fall.
- Will man portablen Code erstellen, so sollte man darauf achten, dass man für den Zugriff auf die Datenbank nur Standard-SQL Syntax verwendet. Der JDBC-Treiber reicht den SQL-Befehl, den man ausführen will, direkt an das Datenbanksystem weiter. Wird die Anwendung auf ein anderes Datenbanksystem migriert, werden die zuvor benutzten SQL-Anweisungen dort eventuell nicht mehr verstanden.
- Einige Treiber verfügen über Erweiterungen, die nicht direkt im JDBC-Standard enthalten sind, wie z. B. auch Oracle (Zugriff auf Large Objects und benutzerdefinierte Typen). Bei der Benutzung von solchen Erweiterungen wird nicht nur mit der JDBC-Schnittstelle programmiert, sondern es werden Klassen benutzt, die nur bei dem verwendeten Treiber verfügbar sind und die auch nur dieser Treiber unterstützt. Bei der Benutzung von derartigen Erweiterungen ist man auf ein Datenbanksystem festgelegt.
In Abbildung 18.27 ist ein Klassendiagramm zu sehen, das zeigt, wie man durch diese Unterteilung relative leicht portierbare Anwendungen erstellen kann.
- Zugriffe, die sich am SQL-Standard orientieren und so problemlos auch auf anderen Datenbanksystem ausgeführt werden können.
- Zugriffe, die durch spezielle Konstrukte optimiert werden können, die nur in Oracle zur Verfügung stehen.
Die Klasse GenericBuchshopDatabaseImpl stellt eine allgemeine Implementierung der Datenbankzugriffe zur Verfügung. Wenn sich einige Zugriffe überhaupt nicht allgemein implementieren lassen, bzw. wenn es einen sehr hohen Aufwand erfordert, einige Funktionen allgemein zu implementieren, kann man die Klasse und die entsprechenden Methoden auch als abstract deklarieren und die Implementierung offen lassen, wie es in folgendem Beispiel gemacht wurde:
package buchshop.server; import java.sql.*; public abstract class GenericBuchshopDatabaseImpl { Connection con; public GenericBuchshopDatabaseImpl() { // hier Initialisierung der Datenbankverbindung } public Produkte[] getProdukte(String kategorie) { // hier Abfrage der Produkte } public abstract Kategorien[] getKategorien(); }Die Methode getKategorien() wurde im Beispiel als abstract deklariert, da es für hierarchische Abfragen keine allgemeingültige Implementierung gibt. Da die aktuelle Implementierung mit Oracle gemacht wird und dort der CONNECT BY-Operator zur Verfügung steht, wird die Implementierung dieser Methode deshalb zunächst offen gelassen. Dadurch wird zunächst unnötige Arbeit vermieden. Später kann diese Methode allerdings einfach ergänzt werden. Von dieser Klasse wird nun eine zweite Klasse OracleBuchshopDatabaseImpl abgeleitet, die die Lücken der Basisklasse füllt, wie in folgendem Listing zu sehen ist:package buchshop.server; import java.sql.*; public class OracleBuchshopDatabaseImpl extends GenericBuchshopDatabaseImpl { public OracleBuchshopDatabaseImpl() { super(); } public Kategorien[] getKategorien() { // hier Abfrage der Kategorien } }Hier kann nun die hierarchische Abfrage in der Methode getKategorien() mit Oracle-spezifischem SQL implementiert werden. Für die Verwendung eines anderen Datenbanksystems, das auch hierarchische Abfrage mit anderen Konstrukten erlaubt, muss lediglich eine zweite Klasse von GenericBuchshopDatabaseImpl abgeleitet werden, die diese Methode implementiert. Eine Implementierung mit DB2 kann hierzu z. B. Gebrauch von den dort verfügbaren Common Table Expressions machen. Bei der späteren Benutzung dieser Zugriffsklassen muss man lediglich wissen, welches Datenbanksystem verwendet werden soll:GenericBuchshopDatabaseImpl db; db = new OracleBuchshopDatabaseImpl(); Kategorien[] k = db.getKategorien();Zunächst wird eine Variable vom Typ GenericBuchshopDatabaseImpl definiert. Danach wird ein Exemplar von OracleBuchshopDatabaseImpl erzeugt und dieser Variablen zugewiesen. Das funktioniert deshalb, da die Oracle-Implementierung von der allgemeinen Implementierung abgeleitet wurde und somit auch ein Exemplar der übergeordneten Klasse ist (wenn auch ein spezielles Exemplar). Da die Oracle-Implementierung keine neuen Methoden definiert, sondern nur welche überschreibt, die bereits in der Oberklasse definiert wurden, verfügt die Oberklasse über genau dieselben Methoden wie die Oracle-Implementierung. Dass eine Methode in der Oberklasse abstract definiert wurde, spielt keine Rolle. Wichtig ist nur, dass sie überhaupt definiert wurde. Wenn man nun die Methode getKategorien() aufruft, wird tatsächlich getKategorien() der Oracle-Implementierung aufgerufen, da ein derartiges Exemplar in der Variable gespeichert wurde. Für die Benutzung einer konkreten Datenbank-Implementierung muss man lediglich ein Exemplar der jeweiligen Implementierungsklasse (z. B. DB2BuchshopDatabaseImpl) erzeugen und in der Variablen speichern.