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).
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" />
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" ) );
}
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.