18.10.3 | Connection Pooling |
Connection Pooling ist eine Technik, die meist in Middleware-Komponenten wie z. B. EJB-/CORBA-Komponenten oder Servlets/JSP eingesetzt wird.
Unter Connection Pooling versteht man generell die Wiederverwendung physischer Datenbankverbindungen. Zu Middleware-Server-Komponenten verbinden sich üblicherweise in kurzen Zeitabständen verschiedene Clients. Wenn eine Serverkomponente auf eine Datenbank zugreift, muss sie für die Datenbank-Operationen eine Datenbankverbindung bereitstellen. Da der Aufbau einer Datenbankverbindung eine relativ ressourcenintensive Operation darstellt, ist es besser, eine Datenbankverbindung im Server nicht sofort zu schließen, wenn ein Client seine Arbeit beendet hat. Sie kann gespeichert und für nachfolgende Client-Zugriffe wiederverwendet werden. Connection Pooling in JDBC 2.0 soll dem Entwickler genau diese Funktionalität zur Verfügung stellen.
Beim Connection Pooling mit dem Oracle-Treiber kann man zwei Stufen unterscheiden:Eine einzelne Pooled Connection implementiert somit den generellen Mechanismus für die Wiederverwendung von Datenbankverbindungen. Der Connection Pool macht von diesem Mechanismus Gebrauch, indem mehrere dieser Verbindungen in einem Cache bereitgestellt werden. Die direkte Programmierung mit Pooled Connections stellt eine Lowlevel-Schnittstelle dar und wird in der Praxis nur für die Implementierung eines Connection Pools mit einem bestimmten Pooling-Mechanismus verwendet (z. B. bei der Implemetierung von JDBC-Treiber-Erweiterungen). Wesentlich anwendungsorientierter hingegen ist die direkte Verwendung der Highlevel-Schnittstelle eines Connection Pools für die Implementierung einer Middleware-Komponente.
- »Pooled Connection«
Eine Pooled Connection repräsentiert eine wiederverwendbare Datenbankverbindung. Sie wird beim Aufruf von close() nicht physisch geschlossen, sondern lediglich freigegeben und bleibt somit für nachfolgende Client-Zugriffe erhalten.- »Connection Pool«
In einem Connection Pool wird eine Menge von Pooled Connections verfügbar gemacht, die bei Bedarf aus dem Pool angefordert und nach der Verwendung wieder in den Pool zurückgestellt werden. Alle Verbindungen im Connection Pool sind dabei mit derselben Datenbank verbunden.
Connection Pooling wird durch einen eigenen JDBC-Datenquellen-Typ unterstützt, die ConnectionPoolDataSource.
JDBC 2.0 definiert das Interface ConnectionPoolDataSource, das eine Schnittstelle für die Erzeugung von Pooled Connections zur Verfügung stellt. Für die Verwendung von Pooled Connections und Connection Pools muss man in der Anwendung zunächst ein Exemplar erzeugen, das dieses Interface implementiert. Oracle stellt hierzu die Klasse OracleConnectionPoolDataSource bereit:... String url = "jdbc:oracle:thin:@localhost:1521:orcl"; OracleConnectionPoolDataSource ds; ds = new OracleConnectionPoolDataSource(); ds.setURL(url); ds.setUser("scott"); ds.setPassword("tiger"); ...OracleConnectionPoolDataSource implementiert sowohl das Interface PooledConnection als auch DataSource. Da im Interface DataSource bereits die Methode getConnection() definiert ist, kann man über diese Klasse neue Verbindungen zum Datenbankserver erzeugen:Connection con = ds.getConnection();Die hierdurch zurückgelieferten Verbindungen unterstützen jedoch noch kein Connection Pooling. Bei jedem Aufruf von getConnection() wird eine neue Verbindung zum Datenbankserver aufgebaut und beim Aufruf von close() wieder geschlossen. Das entpricht dem Verhalten einer herkömmlichen Verbindung. Connection Pooling wird erst durch die im Folgenden beschriebenen Zwischenschritte ermöglicht.
Damit der Zugriff auf den Connection Pool transparent von den zugrundeliegenden Klassen bleibt (z. B. Oracle), werden vorkonfigurierte Exemplare meist über ein JNDI-Verzeichnis zugänglich gemacht. Dabei wird beim Zugriff nicht direkt ein Exemplar der implementierenden Klasse angelegt, wie im obigen Beispiel, sondern nur über einen JNDI-Pfad ein vorkonfiguriertes Exemplar aus dem Verzeichnis abgerufen.
Pooled Connections können zur Implementierung eines Connection Pools eingesetzt werden. Sie implementieren allgemein wiederverwendbare Datenbankverbindungen. Abbildung zeigt die generelle Funktionsweise einer Pooled Connection.Nachdem man einen Verweis auf ein Exemplar der Klasse ConnectionPoolDataSource erhalten hat, kann man durch Aufruf von getPooledConnection() eine neue Verbindung erzeugen:
PooledConnection pc = ds.getPooledConnection("shop", "shop");Bei diesem Aufruf wird eine neue Verbindung zur Datenbank aufgebaut und intern in der Klasse PooledConnection zwischengespeichert. Je nachdem, ob man Benutzer und Passwort bereits in der Datenquelle gesetzt hat, muss man beim Aufruf von getPooledConnection() Benutzer und Passwort übergeben (wie hier im Beispiel).
Die Klasse PooledConnection stellt ihrerseits die Methode getConnection() zur Verfügung, die ein Exemplar vom Typ Connection zurückliefert, das für die Datenbankabfrage benutzt werden kann:Connection con = pc.getConnection(); // Hier Datenbankabfragen ...Mit jeder Pooled Connection ist eine physische Datenbankverbindung assoziiert. Beim Aufruf von getConnection() wird ein logisches Connection-Exemplar erzeugt, das intern die physische Verbindung zugewiesen bekommt. Damit ist die physische Verbindung einem Client zugeordnet. Die logische Verbindung hat die Funktion einer Wrapperklasse und leitet intern die Aufrufe an die physische Verbindung weiter. Hat der Client seine Arbeit beendet und die Verbindung wird nicht mehr benötigt, ruft er die close()-Methode der logischen Verbindung auf. Dadurch wird allerdings nicht wirklich die physische Verbindung zur Datenbank geschlossen. Diese wird lediglich wieder freigegeben.
Man sollte beachten, dass Pooled Connections keine Synchronisations-Mechanismen besitzen. Erfolgt ein zweiter Aufruf von getConnection(), bevor die erste Verbindung mit close() geschlossen wurde, wird die physische Verbindung der gerade aktiven logischen Verbindung entzogen und der neu erzeugten Verbindung zugewiesen. Bei nachfolgenden Zugriffen auf die erste Verbindung wird eine SQL-Exception mit der Fehlermeldung »Logisches Handle nicht mehr gültig« ausgelöst.
Ein Connection Pool enthält eine Menge an physischen Datenbankverbindungen zur selben Datenbank, die auf Abruf zur Ausführung von SQL-Anweisungen bereitgestellt werden. Wenn die Arbeit mit einer Verbindung beendet ist, wird sie nicht geschlossen, sondern wieder zurück in den Pool freier Verbindungen gestellt. Der große Vorteil eines Connection Pools gegenüber einer herkömmlichen Verbindung ist, dass initial einige wenige Verbindungen geöffnet und danach temporär auf Abruf einzelnen Clients zur Verfügung gestellt werden. Durch diese Technik können relativ viele Clients mit relativ wenigen Datenbankverbindungen arbeiten. Das Prinzip wird z. B. auch vom Oracle-Server im MTS-Modus eingesetzt. Der Server startet initial eine vordefinierte Anzahl an Dispatcher-Prozessen, die bei Bedarf bei Client-Zugriffen temporär einzelnen Clients für die Verarbeitung der Anfragen zur Verfügung gestellt werden. Hat der Client seine Arbeit beendet, ist der Dispatcher frei und kann die Anfrage eines anderen Clients bearbeiten.
In Abbildung 18.25 ist der Ablauf bei der Einrichtung eines Connection Pools mit dem Oracle-Treiber dargestellt.Beim Oracle-JDBC-Treiber wird eine Implementierung eines Connection Pools mit der Klasse OracleConnectionCacheImpl mitgeliefert. Diese Klasse implementiert das Interface DataSource und kann deshalb nach der Initialisierung wie eine herkömmliche Datenquelle benutzt werden.
Bei der Initialisierung wird zunächst ein neues Exemplar der Klasse OracleConnectionCacheImpl erzeugt und mit den Verbindungsparametern initialisiert:... ConnectionPoolDataSource ds; ds = (ConnectionPoolDataSource)ic.lookup( "jdbc_access://jdbc/shopds"); OracleConnectionCacheImpl pool = new OracleConnectionCacheImpl(ds); pool.setUser("shop"); pool.setPassword("shop"); pool.setMaxLimit(5); Connection con = pool.getConnection(); // Hier Ausführen von SQL // ...Bei der Initialisierung wird in jedem Fall ein Exemplar der Klasse ConnectionPoolDataSource benötigt. Es kann entweder direkt dem Konstruktor übergeben oder später mit der Methode setConnectionPoolDataSource() gesetzt werden. Das ConnectionPoolDataSource-Exemplar dient dem Connection Pool intern als Factory-Objekt für die Erzeugung von Pooled Connections. Dem Connection Pool kann man außerdem Verbindungsinformationen sowie Einstellungen übergeben, die den Pool selbst betreffen. Im obigen Beispiel wird die generelle Verbindungsinformation (Rechnername, Portnummer, Treiber, SID) aus der übergebenen ConnectionPoolDataSource übernommen. Diese Parameter können jedoch über entsprechende Methoden auch neu gesetzt werden. Mit der Methode setMaxLimit() wird im obigen Beispiel die maximale Anzahl an physischen Verbindungen im Connection Pool gesetzt (und somit auch von Pooled Connections, da jede physische Verbindung in einer Pooled Connection gekapselt wird).
Beim Aufruf von getConnection() liefert ein Connection Pool letztendlich eine logische Verbindung zurück, die bei der Verarbeitung von Client-Anfragen für die Ausführung von SQL-Anweisungen benutzt werden kann. Wird eine logische Verbindung geschlossen, wird die physische Verbindung wieder zurück in den Pool freier Verbindungen gegeben und steht für weitere Anfragen zur Verfügung.
Da ein Connection Pool intern mehrere physische Verbindungen speichert, können gleichzeitig mehrere Verbindungen aktiv sein. Sind alle Verbindungen belegt und wird versucht, mit getConnection() eine weitere Verbindung aus dem Connection Pool zu erhalten, tritt eine Ausnahmesituation ein. Wie sie behandelt wird, kann man mit der Methode setCacheScheme() einstellen, wie folgendes Beispiel zeigt:OracleConnectionCacheImpl pool = new OracleConnectionCacheImpl(ds); pool.setCacheScheme(OracleConnectionCacheImpl.FIXED_WAIT_SCHEME);Diese Methode erhält als Parameter eine Konstante übergeben, die stellvertretend für eine der drei Verarbeitungs-Varianten eingesetzt wird. Die Konstanten sind alle in der Klasse OracleConnectionCacheImpl definiert und werden im Folgenden aufgelistet:Betrachtet wird jeweils der Fall, dass bereits die maximale Anzahl an Verbindungen im Connection Pool belegt ist und versucht wird, durch Aufruf von getConnection() eine weitere Verbindung zu bekommen.
- DYNAMIC_SCHEME
Ist das maximale Limit erreicht, wird eine weitere physische Verbindung erzeugt und über eine Pooled Connection an den Aufrufer zurückgeliefert. Beim Schließen dieser Verbindung wird sie jedoch nicht zurück in den Pool gestellt, sondern sofort physisch geschlossen. Dadurch stehen im Pool von außen jederzeit genügend Verbindungen zur Verfügung, zeitweise jedoch mehr als die maximale Anzahl. Nach der Initialisierung ist dieser Wert voreingestellt.- FIXED_RETURN_NULL_SCHEME
Liefert den Wert null zurück, wenn die maximale Anzahl an Verbindungen bereits erreicht ist.- FIXED_WAIT_SCHEME
Beim Abrufen einer weiteren Verbindung wird der Aufrufer so lange blockiert, bis eine der belegten Verbindungen durch close() geschlossen und somit in den Pool zurückgestellt wird.
Mit der Methode setMinLimit() kann man außerdem eine minimale Verbindungsanzahl im Pool spezifizieren. Die Anzahl der aktuell geöffneten Verbindungen im Pool befindet sich immer zwischen den Werten, die durch setMaxLimit() und setMinLimit() zugewiesen wurden. Nach der Initialisierung beträgt bei der Oracle-Implementierung die minimale Anzahl an Verbindungen 0, die maximale Anzahl 10. Je nachdem, wie häufig Verbindungen aus dem Pool angefordert werden, können zeitweise mehr oder weniger Verbindungen aktiv sein. Die Anzahl der gerade aktiven Verbindungen aus dem Pool kann man mit der Methode getActiveSize(), die aktuelle Pool-Größe mit getCacheSize() ermitteln.