Datenbankloader konfigurieren

Loader (Ladeprogramme) sind BackingMap-Plug-ins, die aufgerufen werden, wenn Änderungen an der BackingMap vorgenommen werden oder wenn die BackingMap eine Datenanforderung nicht bedienen kann (Cachefehler).

Hinweise zum Vorherigen Laden

Loader (Ladeprogramme) sind BackingMap-Plug-ins, die aufgerufen werden, wenn Änderungen an der BackingMap vorgenommen werden oder wenn die BackingMap eine Datenanforderung nicht bedienen kann (Cachefehler). Eine Übersicht über die Interaktion von eXtreme Scale mit einem Loader finden Sie unter Inline-Cache.

Jede BackingMap hat ein boolesches Attribut "preloadMode", mit dem festgelegt werden kann, ob das vorherige Laden (Preload) einer Map asynchron durchgeführt wird oder nicht. Standardmäßig ist das "preloadMode" auf "false" gesetzt, d. h., die Initialisierung der BackingMap ist erst abgeschlossen, wenn das vorherige Laden der Map abgeschlossen ist. Die Initialisierung der BackingMap ist beispielsweise erst abgeschlossen, wenn die Methode preloadMap zurückkehrt. Wenn die Methode "preloadMap" sehr viele Daten aus ihrem Back-End liest und in die Map lädt, kann dieser Vorgang relativ lang dauern. In diesem Fall können Sie eine BackingMap für das asynchrone vorherige Laden der Map konfigurieren, indem Sie das Attribut "preloadMode" auf "true" setzen. Diese Einstellung bewirkt, dass der Initialisierungscode der BackingMap einen Thread startet, der die Methode preloadMap aufruft. Auf diese Weise kann die Initialisierung einer BackingMap abgeschlossen werden, während das vorherige Laden (Preload) der Map noch läuft.

In einem verteilten eXtreme-Scale-Szenario ist eines der Preload-Muster der Client-Preload. Im Client-Preload-Muster ist ein eXtreme-Scale-Client für den Abruf der Daten vom Back-End und das anschließende Einfügen der Daten in den verteilten eXtreme-Scale-Server unter Verwendung von DataGrid-Agenten verantwortlich. Außerdem kann ein Client-Preload in der Methode "Loader.preloadMap" in nur einer einzigen Partition ausgeführt werden. In diesem Fall wird das asynchrone Laden der Daten in das Grid sehr wichtig. Wenn der Client-Preload in demselben Thread ausgeführt wird, wird die BackingMap nie initialisiert und die Partition mit der BackingMap somit niemals online gesetzt. Deshalb kann der eXtreme-Scale-Client die Anforderung nicht an die Partition senden, was schließlich zu einer Ausnahme führt.

Wenn ein eXtreme-Scale-Client in der Methode preloadMap verwendet wird, müssen Sie das Attribut preloadMode auf "true" setzen. Alternativ können Sie einen Thread im Client-Preload-Code starten.

Das folgende Code-Snippet veranschaulicht, wie das Attribut "preloadMode" so gesetzt wird, dass das asynchrone vorherige Laden aktiviert wird:

BackingMap bm = og.defineMap( "map1" );
bm.setPreloadMode( true );

Das Attribut "preloadMode" kann auch über eine XML-Datei gesetzt werden, wie im folgenden Beispiel demonstriert wird:

<backingMap name="map1" preloadMode="true" pluginCollectionRef="map1" 
	lockStrategy="OPTIMISTIC" />

TxID und die Verwendung der Schnittstelle "TransactionCallback"

Die Methode get und die batchUpdate-Methoden in der Schnittstelle "Loader" werden an ein TxID-Objekt übergeben, das die Session-Transaktion darstellt, die die Ausführung der Operation get bzw. batchUpdate voraussetzt. Die Methoden get und batchUpdate können in einer Transaktion mehrfach aufgerufen werden. Deshalb werden transaktionsbezogene Objekte, die der Loader benötigt, gewöhnlich in einem Slot des TxID-Objekts verwaltet. Ein JDBC-Loader (Java Database Connectivity) wird verwendet, um zu veranschaulichen, wie ein Loader die Schnittstellen "TxID" und "TransactionCallback" verwendet.

Es können mehrere ObjectGrid-Maps in derselben Datenbank gespeichert werden. Jede Map hat einen eigenen Loader, und jeder Loader muss möglicherweise eine Verbindung zu derselben Datenbank herstellen. Wenn die Loader eine Verbindung zur Datenbank herstellen, müssen sie dieselbe JDBC-Verbindung verwenden. Über diese Verbindung werden die Änderungen im Rahmen derselben Datenbanktransaktion in jeder Tabelle festgeschrieben. Gewöhnlich schreibt die Person, die die Loader-Implementierung schreibt, auch die TransactionCallback-Implementierung. Bei der Erweiterung der Schnittstelle TransactionCallback empfiehlt es sich, Methoden hinzuzufügen, die der Loader benötigt, um eine Datenbankverbindung anzufordern und vorbereitete Anweisungen zwischenzuspeichern. Der Grund für diese Vorgehensweise wird deutlich, wenn Sie sehen, wie die Schnittstellen "TransactionCallback" und "TxID" vom Loader verwendet werden.

Beispiel: Der Loader erfordert eine Erweiterung der Schnittstelle "TransactionCallback" wie die folgende:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.ibm.websphere.objectgrid.TxID;
public interface MyTransactionCallback extends TransactionCallback
{
    Connection getAutoCommitConnection(TxID tx, String databaseName) throws SQLException;
    Connection getConnection(TxID tx, String databaseName, int isolationLevel ) throws SQLException;
    PreparedStatement getPreparedStatement(TxID tx, Connection conn, String tableName, String sql) throws SQLException;
    Collection getPreparedStatementCollection( TxID tx, Connection conn, 	String tableName );
}

Mit Hilfe dieser neuen Methoden können die Loader-Methoden "get" und batchUpdate wie folgt eine Verbindung anfordern:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.ibm.websphere.objectgrid.TxID;
private Connection getConnection(TxID tx, int isolationLevel)
{
    Connection conn = ivTcb.getConnection(tx, databaseName, isolationLevel );
    return conn;
}

Im vorherigen Beispiel und den Beispielen, die noch folgen, sind "ivTcb" und "ivOcb" Instanzvariablen des Loaders, die gemäß der Beschreibung im Abschnitt "Hinweise zum vorherigen Laden" beschrieben wurden. Die Variable "ivTcb" ist eine Referenz auf die Schnittstelle "MyTransactionCallback" und die Variable "ivOcb"eine Referenz auf die MyOptimisticCallback-Instanz. Die Variable "databaseName" ist eine Instanzvariable des Loaders, die als Loader-Eigenschaft während der Initialisierung der BackingMap gesetzt wurde. Das Argument "isolationLevel" ist eine der Konstanten der JDBC-Verbindung, die für die verschiedenen von JDBC unterstützten Isolationsstufen definiert sind. Wenn der Loader eine optimistische Implementierung nutzt, verwendet die Methode "get" gewöhnlich eine JDBC-Verbindung mit automatischem Festschreiben, um die Daten aus der Datenbank abzurufen. In diesem Fall kann der Loader eine Methode "getAutoCommitConnection" haben, die wie folgt implementiert ist:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.ibm.websphere.objectgrid.TxID;
private Connection getAutoCommitConnection(TxID tx)
{
    Connection conn = ivTcb.getAutoCommitConnection(tx, databaseName);
    return conn;
}

Rufen Sie die Methode "batchUpdate" wieder auf, die die folgende switch-Anweisung enthält:

switch ( logElement.getType().getCode() )
{
    case LogElement.CODE_INSERT:
        buildBatchSQLInsert( tx, key, value, conn );
        break;
    case LogElement.CODE_UPDATE:
        buildBatchSQLUpdate( tx, key, value, conn );
        break;
    case LogElement.CODE_DELETE:
        buildBatchSQLDelete( tx, key, conn );
        break;
}

Jede der buildBatchSQL-Methoden verwendet die Schnittstelle "MyTransactionCallback", um eine vorbereitete Anweisung abzurufen. Im Folgenden sehen Sie ein Code-Snippet, das die Methode "buildBatchSQLUpdate" zeigt, die eine SQL-Anweisung "update" für die Aktualisierung eines EmployeeRecord-Eintrags und das Hinzufügen dieses Eintrags für die Aktualisierung im Stapelbetrieb erstellt:

private void buildBatchSQLUpdate( TxID tx, Object key, Object value, Connection conn ) 
throws SQLException, LoaderException
{
    String sql = "update EMPLOYEE set LASTNAME = ?, FIRSTNAME = ?, DEPTNO = ?,
    SEQNO = ?, MGRNO = ? where EMPNO = ?";
    PreparedStatement sqlUpdate = ivTcb.getPreparedStatement( tx, conn, 
			"employee", sql );
    EmployeeRecord emp = (EmployeeRecord) value;
    sqlUpdate.setString(1, emp.getLastName());
    sqlUpdate.setString(2, emp.getFirstName());
    sqlUpdate.setString(3, emp.getDepartmentName());
    sqlUpdate.setLong(4, emp.getSequenceNumber());
    sqlUpdate.setInt(5, emp.getManagerNumber());
    sqlUpdate.setInt(6, key);
    sqlUpdate.addBatch();
}

Nachdem die batchUpdate-Schleife alle vorbereiteten Anweisungen erstellt hat, ruft sie die Methode "getPreparedStatementCollection" auf. Diese Methode ist wie folgt implementiert:

private Collection getPreparedStatementCollection( TxID tx, Connection conn )
{
    return ( ivTcb.getPreparedStatementCollection( tx, conn, "employee" ) );
}
Wenn die Anwendung die Methode "commit" in Session aufruft, ruft der Session-Code die Methode "commit" in der Methode "TransactionCallback" auf, nachdem sie alle von der Transaktion vorgenommenen Änderungen mit Push an den Loader für jede Map übertragen hat, die von der Transaktion geändert wurde. Da alle Loader die Methode "MyTransactionCallback" verwenden, um die erforderlichen Verbindungen und vorbereiteten Anweisungen abzurufen, weiß die Methode "TransactionCallback", welche Verbindung verwendet werden muss, um die Festschreibung der Änderungen im Back-End anzufordern. Die Erweiterung der Schnittstelle "TransactionCallback" mit den einzelnen von den Loadern benötigten Methoden hat somit die folgenden Vorteile:
  • Das TransactionCallback-Objekt kapselt die Verwendung von TxID-Slots für transaktionsbezogene Daten, und der Loader erfordert keine Informationen zu den TxID-Slots. Der Loader muss lediglich die Methoden kennen, die TransactionCallback über die Schnittstelle "MyTransactionCallback" für die vom Loader benötigten unterstützenden Funktionen hinzugefügt werden.
  • Das TransactionCallback-Objekt kann sicherstellen, dass die gemeinsame Verbindungsnutzung für alle Loader, die Verbindungen zu demselben Back-End herstellen, stattfindet, so dass ein zweiphasiges Festschreibungsprotokoll umgangen werden kann.
  • Das TransactionCallback-Objekt kann über eine Commit- oder Rollback-Operation, die bei Bedarf in der Verbindung aufgerufen wird, sicherstellen, dass die Verbindungsherstellung zum Back-End abgeschlossen wird.
  • TransactionCallback stellt sicher, dass eine Bereinigung der Datenbankressourcen stattfindet, wenn eine Transaktion abgeschlossen wird.
  • TransactionCallback verheimlicht, wenn eine verwaltete Verbindung von einer verwalteten Umgebung wie WebSphere Application Server oder einem anderen J2EE-kompatiblen (Java 2 Platform, Enterprise Edition) Anwendungsserver abgerufen wird. Auf diese Weise kann derselbe Loader-Code in verwalteten und nicht verwalteten Umgebungen verwendet werden. Nur das TransactionCallback-Plug-in muss geändert werden.
  • Ausführliche Informationen zur Verwendung der TxID-Slots für transaktionsbezogene Daten durch die TransactionCallback-Implementierung finden Sie in der Beschreibung des TransactionCallback-Plug-ins.

OptimisticCallback

Wie bereits erwähnt, kann der Loader einen optimistischen Ansatz für die Steuerung des gemeinsamen Zugriffs verwenden. In diesem Fall muss das Beispiel für die Methode "buildBatchSQLUpdate" geringfügig geändert werden, um einen optimistischen Ansatz zu implementieren. Es gibt verschiedene Methoden für die Verwendung eines optimistischen Ansatzes. Eine typische Methode ist die Verwendung einer Zeitmarken- oder Folgenummernspalte für die Versionssteuerung jeder Aktualisierung der Spalte. Angenommen, die Tabelle "employee" hat eine Folgenummernspalte, deren Wert jedesmal um eins erhöht wird, wenn die Zeile aktualisiert wird. In diesem Fall können Sie die Signatur der Methode "buildBatchSQLUpdate" so ändern, dass das LogElement-Objekt an Stelle des Schlüssel/Wert-Paars an sie übergeben wird. Außerdem muss sie das OptimisticCallback-Objekt verwenden, das in die BackingMap integriert wird, um das Anfangsversionsobjekt abzurufen und das Versionsobjekt zu aktualisieren. Im Folgenden sehen Sie ein Beispiel für eine geänderte Methode "buildBatchSQLUpdate", die die Instanzvariable "ivOcb" verwendet, die gemäß der Beschreibung im Abschnitt zu preloadMap initialisiert wurde:

Beispiel für den geänderten Code der Methode batch-update
private void buildBatchSQLUpdate( TxID tx, LogElement le, Connection conn )
	throws SQLException, LoaderException
{
    // Anfangsversionsobjekt abrufen, wenn dieser Map-Eintrag
    // in der Datenbank gelesen oder aktualisiert wurde.
    Employee emp = (Employee) le.getCurrentValue();
    long initialVersion = ((Long) le.getVersionedValue()).longValue();
    // Versionsobjekt aus dem aktualisierten Employee-Objekt für die
    // SQL-Operation "update" abrufen.
    Long currentVersion = (Long)ivOcb.getVersionedObjectForValue( emp );
    long nextVersion = currentVersion.longValue();
    // Jetzt die SQL-Operation "update" erstellen, die das Versionsobjekt
    // in der WHERE-Klausel für optimistische Prüfung enthält.
    String sql = "update EMPLOYEE set LASTNAME = ?, FIRSTNAME = ?,
    DEPTNO = ?,SEQNO = ?, MGRNO = ? where EMPNO = ? and SEQNO = ?";
    PreparedStatement sqlUpdate = ivTcb.getPreparedStatement( tx, conn, 
			"employee", sql );
    sqlUpdate.setString(1, emp.getLastName());
    sqlUpdate.setString(2, emp.getFirstName());
    sqlUpdate.setString(3, emp.getDepartmentName());
    sqlUpdate.setLong(4, nextVersion );
    sqlUpdate.setInt(5, emp.getManagerNumber());
    sqlUpdate.setInt(6, key);
    sqlUpdate.setLong(7, initialVersion);
    sqlUpdate.addBatch();
}

Das Beispiel zeigt, dass das LogElement-Objekt verwendet wird, um den Anfangsversionswert abzurufen. Wenn die Transaktion zum ersten Mal auf den Map-Eintrag zugreift, wird ein LogElement-Objekt mit dem Anfangs-Employee-Objekt erstellt, das aus der Map abgerufen wurde. Außerdem wird das Anfangs-Employee-Objekt an die Methode "getVersionedObjectForValue" in der Schnittstelle "OptimisticCallback" übergeben und das Ergebnis im LogElement-Objekt gespeichert. Diese Verarbeitung findet statt, bevor eine Anwendung eine Referenz auf das Anfangs-Employee-Objekt erhält und die Chance hat, eine Methode aufzurufen, die den Status des Anfangs-Employee-Objekts zu ändern.

Das Beispiel zeigt, dass der Loader die Methode "getVersiondObjectForValue" verwendet, um das Versionsobjekt für das aktuelle aktualisierte Employee-Objekt abzurufen. Vor dem Aufruf der Methode "batchUpdate" in der Schnittstelle "Loader" ruft eXtreme Scale die Methode "updateVersionedObjectForValue" in der Schnittstelle "OptimisticCallback" auf, um die Generierung eines neuen Versionsobjekts für das aktualisierte Employee-Objekt anzufordern. Wenn die Methode "batchUpdate" zu ObjectGrid zurückkehrt, wird das LogElement-Objekt mit dem aktuellen Versionsobjekt aktualisiert und als neues Anfangsversionsobjekt festgelegt. Dieser Schritt ist erforderlich, weil die Anwendung die Methode "flush" für die Map an Stelle der Methode "commit" für die Session aufgerufen haben kann. Der Loader kann von einer einzelnen Transaktion mehrfach für denselben Schlüssel aufgerufen werden. Aus diesem Grund stellt eXtreme Scale sicher, dass das LogElement-Objekt jedesmal, wenn die Zeile in der Tabelle "employee" aktualisiert wird, mit dem neuen Versionsobjekt aktualisiert wird.

Jetzt hat der Loader das Anfangsversionsobjekt und das Folgeversionsobjekt und kann eine SQL-Anweisung "SQL" ausführen, die die Spalte SEQNO auf den Wert des Folgeversionsobjekts setzt und den Wert des Anfangsversionsobjekts in der WHERE-Klausel verwendet. Dieser Ansatz wird manchmal auch als überqualifizierte update-Anweisung bezeichnet. Die Verwendung der überqualifizierten update-Anweisung ermöglicht der relationalen Datenbank sicherzustellen, dass die Zeile in der Zeit zwischen dem Lesen der Daten aus der Datenbank und dem Aktualisieren der Datenbank durch die Transaktion nicht geändert wurde. Wenn die Zeile von einer anderen Transaktion geändert wurde, gibt Zählerbereich, der von der Aktualisierung im Stapelbetrieb zurückgegeben wird, an, dass keine Zeilen für diesen Schlüssel geändert wurden. Der Loader muss sicherstellen, dass die SQL-Operation "update" die Zeile geändert hat. Ist dies nicht der Fall, zeigt der Loader eine Ausnahme vom Typ "com.ibm.websphere.objectgrid.plugins.OptimisticCollisionException" an, um das Session-Objekt darüber zu informieren, dass die Methode "batchUpdate" fehlgeschlagen ist, weil mehrere gleichzeitig ausgeführte Transaktionen versuchen, dieselbe Zeile in der Datenbanktabelle zu aktualisieren. Diese Ausnahme bewirkt, dass eine Rollback-Operation für das Session-Objekt durchgeführt wird, und die Anwendung muss die vollständige Transaktion wiederholen. Die Begründung ist, dass die Wiederholung erfolgreich ist, und deshalb wird dieser Ansatz auch als optimistischer Ansatz bezeichnet. Der optimistische Ansatz bietet eine bessere Leistung, wenn die Daten nur selten geändert werden und nur selten gleichzeitig ausgeführte Transaktionen versuchen, dieselbe Zeile zu aktualisieren.

Es ist wichtig, dass der Loader mit dem Parameter "key" des Konstruktors "OptimisticCollisionException" angibt, welcher Schlüssel bzw. welche Gruppe von Schlüsseln für das Fehlschlagen der optimistischen batchUpdate-Methode verantwortlich ist. Der Parameter "key" kann das Schlüsselobjekt selbst oder ein Bereich von Schlüsselobjekten sein, falls mehrere Schlüssel zum Fehlschlagen der optimistischen Aktualisierung geführt haben. eXtreme Scale verwendet die Methode "getKey" des Konstruktors "OptimisticCollisionException", um festzustellen, welche Map-Einträge veraltete Daten enthalten und deshalb zur Ausnahme geführt haben. Ein Teil der Rollback-Verarbeitung besteht darin, dass alle veralteten Map-Einträge aus der Map entfernt werden. Das Entfernen veralteter Einträge ist erforderlich, damit alle nachfolgenden Transaktionen, die auf dieselben Schlüssel zugreifen, bewirken, dass die Methode "get" der Schnittstelle "Loader" aufgerufen wird, um die Map-Einträge mit den aktuellen Daten aus der Datenbank zu aktualisieren.

Im Folgenden sind weitere Möglichkeiten beschrieben, mit denen ein Loader einen optimistischen Ansatz implementieren kann:
  • Es ist keine Zeitmarken- oder Folgenummernspalte vorhanden. In diesem Fall gibt die Methode "getVersionObjectForValue" in der Schnittstelle "OptimisticCallback" einfach das Wertobjekt selbst als Version zurück. Bei diesem Ansatz muss der Loader eine WHERE-Klausel erstellen, die alle Felder des Anfangsversionsobjekts enthält. Dieser Ansatz ist nicht effizient, und nicht alle Spaltentypen eignen sich für die Verwendung in der WHERE-Klausel einer überqualifizierten SQL-Anweisung "update". Deshalb wird dieser Ansatz gewöhnlich nicht verwendet.
  • Es ist keine Zeitmarken- oder Folgenummernspalte vorhanden. Anders als beim vorherigen Ansatz enthält die WHERE-Klausel jedoch die Wertfelder, die von der Transaktion geändert wurden. Eine Methode zur Erkennung der geänderten Felder ist das Einstellen des Kopiermodus in der BackingMap auf "CopyMode.COPY_ON_WRITE". Dieser Kopiermodus erfordert, dass eine Value-Schnittstelle an die Methode "setCopyMode" in der Schnittstelle "BackingMap" übergeben wird. Die BackingMap erstellt dynamische Proxy-Objekte, die die angegebene Value-Schnittstelle implementieren. Mit diesem Kopiermodus kann der Loader jeden Wert in ein com.ibm.websphere.objectgrid.plugins.ValueProxyInfo-Objekt umsetzen. Die Schnittstelle "ValueProxyInfo" hat eine Methode, die dem Loader ermöglicht, die Liste der Attributnamen abzurufen, die von der Transaktion geändert wurden. Mit dieser Methode kann der Loader die get-Methoden in der Value-Schnittstelle für die Attributnamen aufrufen, um die geänderten Daten abzurufen und eine SQL-Anweisung "update" zu erstellen, die nur die geänderten Attribute setzt. Die WHERE-Klausel kann jetzt so erstellt werden, dass sie die Primärschlüsselspalte und alle geänderten Attributspalten enthält. Dieser Ansatz ist effizienter als der vorherige Ansatz, erfordert aber, dass mehr Code im Loader geschrieben werden muss, und kann einen größeren Cache für vorbereitete Anweisungen erfordern, damit die verschiedenen Permutationen behandelt werden können. Diese Einschränkung sollte jedoch kein Problem darstellen, wenn Transaktionen gewöhnlich nur wenige Attribute ändern.
  • Einige relationale Datenbanken haben eine API für die Unterstützung der automatischen Verwaltung von Spaltendaten, die für eine optimistische Versionssteuerung hilfreich ist. Lesen Sie in der Dokumentation zu Ihrer Datenbank nach, ob diese Möglichkeit existiert.