Les chargeurs sont des plug-in de mappe de sauvegarde appelés lorsque des modifications sont apportées à la mappe de sauvegarde ou lorsque cette dernière est dans l'impossibilité de répondre à une demande de données (absence dans le cache).
Les chargeurs sont des plug-in de mappe de sauvegarde appelés lorsque des modifications sont apportées à la mappe de sauvegarde ou lorsque cette dernière est dans l'impossibilité de répondre à une demande de données (absence dans le cache). Pour une présentation de la manière dont eXtreme Scale interagit avec un chargeur, voir Cache en ligne.
Chaque mappe de sauvegarde dispose d'un attribut booléen preloadMode qui est défini pour indiquer si le préchargement d'une mappe s'exécute de manière asynchrone. Par défaut, l'attribut preloadMode est défini sur false, ce qui signifie que l'initialisation de la mappe de sauvegarde ne s'effectue que si le préchargement de la mappe est terminé. Par exemple, l'initialisation de la mappe de sauvegarde n'est pas complète tant que la méthode preloadMap s'exécute. Si la méthode preloadMap reconnaît une grande quantité de données en provenance de son programme d'arrière plan et les charge dans la mappe, son exécution peut prendre un certain temps. Dans ce cas, vous pouvez configurer l'utilisation du préchargement asynchrone pour une mappe de sauvegarde donnée en définissant l'attribut preloadMode sur true. Ce paramètre amène le code d'initialisation de la mappe de sauvegarde à démarrer une unité d'exécution qui appelle la méthode preloadMap pour permettre d'initialiser une mappe de sauvegarde lorsque le préchargement de la mappe est en cours.
Dans un scénario eXtreme Scale réparti, l'un des motifs de préchargement est le préchargement client. Dans le modèle de préchargement client, un client eXtreme Scale est responsable de l'extraction des données à partir du programme d'arrière-plan et de l'insertion des données dans le serveur de conteneur réparti à l'aide d'agents DataGrid. En outre, le préchargement client peut être exécuté dans la méthode Loader.preloadMap dans une seule et unique partition spécifique. Dans ce cas, le chargement asynchrone des données dans la grille devient crucial. Si le préchargement client était exécuté dans la même unité d'exécution, la mappe de sauvegarde ne serait jamais initialisée, de sorte que la partition dans laquelle elle réside ne passerait jamais au statut ONLINE. Par conséquent, le client eXtreme Scale ne pourrait pas envoyer la demande à la partition, ce qui provoquerait une exception au final.
Si un client eXtreme Scale est utilisé dans la méthode preloadMap, vous devez affecter la valeur true à l'attribut preloadMode. Il est également possible de lancer une unité d'exécution dans le code de préchargement du client.
Le fragment de code ci-dessous illustre comment l'attribut preloadMode est défini pour activer le préchargement asynchrone :
BackingMap bm = og.defineMap( "map1" );
bm.setPreloadMode( true );
L'attribut preloadMode peut également être défini à l'aide d'un fichier XML tel qu'illustré dans l'exemple suivant :
<backingMap name="map1" preloadMode="true" pluginCollectionRef="map1"
lockStrategy="OPTIMISTIC" />
Les méthodes get et batchUpdate sur l'interface Loader reçoivent un objet TxID qui représente la transaction Session nécessitant d'exécuter l'opération get ou batchUpdate. Les méthodes get et batchUpdate peuvent être appelées plusieurs fois par transaction. Par conséquent, les objets inclus dans l'étendue de la transaction nécessaires au chargeur sont généralement conservés dans un emplacement de l'objet TxID. Un chargeur JDBC (Java Database Connectivity) est utilisé pour illustrer comment un chargeur utilise l'objet TxID et les interfaces TransactionCallback.
Plusieurs mappes ObjectGrid peuvent être stockées dans la même base de données. Chaque mappe possède son propre chargeur, et chaque chargeur peut devoir se connecter à la même base de données. Lorsque les chargeurs se connectent à la base de données, ils doivent utiliser la même connexion JDBC pour valider les modifications dans chaque table de la même transaction de base de données. En règle générale, la personne qui écrit l'implémentation Loader écrit également l'implémentation de TransactionCallback. La meilleure méthodes est lorsque l'interface TransactionCallback est étendue pour ajouter des méthodes requises par le chargeur pour l'obtention d'une connexion à la base de données et pour mettre en cache les instructions préparées. Cette méthode devient une évidence lorsque vous constatez comment les interfaces TransactionCallback et TxID sont utilisées par le chargeur.
Par exemple, le chargeur peut nécessiter l'extension de l'interfaceTransactionCallback de la manière suivante :
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 );
}
A l'aide de ces nouvelles méthodes, les méthodes Loader get et batchUpdate peuvent obtenir une connexion comme suit :
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;
}
Dans l'exemple précédent et dans les exemples qui suivent, ivTcb et ivOcb sont des variables d'instance du chargeur qui ont été initialisées tel que décrit dans la section Remarques sur le préchargement. La variable ivTcb est une référence à l'instance MyTransactionCallback et la variable ivOcb est une référence à l'instance MyOptimisticCallback. La variable databaseName est une variable d'instance du chargeur qui a été définie en tant que propriété du chargeur lors de l'initialisation de la mappe de sauvegarde. L'argument isolationLevel est l'une des constantes de la connexion JDBC définies pour les différents niveaux d'isolement pris en charge par JDBC. Si le chargeur utilise une implémentation optimiste, la méthode get utilise généralement une connexion JDBC de validation automatique pour extraire les données de la base de données. Dans ce cas, il se peut que le chargeur possède une méthode getAutoCommitConnection implémentée de la façon suivante :
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;
}
Rappelez-vous que la méthode batchUpdate comporte l'instruction switch suivante :
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;
}
Chacun méthode buildBatchSQL utilise l'interface MyTransactionCallback pour obtenir une instruction préparée. Le fragment de code ci-dessous présente la méthode buildBatchSQLUpdate créant une instruction SQL update pour mettre à jour une entrée EmployeeRecord et l'ajouter pour la mise à jour par lots :
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();
}
Une fois que la boucle batchUpdate a créé toutes les instructions préparées, elle appelle la méthode getPreparedStatementCollection. Cette dernière est implémentée comme suit :
private Collection getPreparedStatementCollection( TxID tx, Connection conn )
{
return ( ivTcb.getPreparedStatementCollection( tx, conn, "employee" ) );
}
Comme mentionné précédemment, le chargeur peut utiliser une approche optimiste pour le contrôle des accès simultanés. Dans ce cas, l'exemple de la méthode buildBatchSQLUpdate doit être légèrement modifié pour l'implémentation d'une approche optimiste. Il existe différentes façons pour mettre en oeuvre une approche optimiste. La façon la plus courante consiste à disposer d'une colonne d'horodatage ou d'une colonne de compteur de numéros de séquence pour la gestion des versions de mise à jour de la ligne. Supposons que la table employee comporte une colonne de numéros de séquence qui s'incrémentent à chaque mise à jour de la ligne. Vous modifiez ensuite la signature de la méthode buildBatchSQLUpdate pour qu'elle soit transmise à l'objet LogElement et non à la paire clé-valeur. Elle doit également utiliser l'objet OptimisticCallback relié à la mappe de sauvegarde pour l'obtention de l'objet de version initiale et la mise à jour de l'objet de version. L'exemple suivant est celui d'une méthode buildBatchSQLUpdate modifiée qui utilise la variable d'instance ivOcb initialisée tel que décrit dans la section preloadMap :
modified batch-update method code example
private void buildBatchSQLUpdate( TxID tx, LogElement le, Connection conn )
throws SQLException, LoaderException
{
// Obtenir l'objet de version initiale lors de la dernière lecture ou mise à jour de cette entrée
// de mappe dans la base de données.
Employee emp = (Employee) le.getCurrentValue();
long initialVersion = ((Long) le.getVersionedValue()).longValue();
// Obtenir l'objet de version de l'objet Employee mis à jour pour l'opération SQL
//update.
Long currentVersion = (Long)ivOcb.getVersionedObjectForValue( emp );
long nextVersion = currentVersion.longValue();
// Créer maintenant l'instruction SQL update qui inclut l'objet de version dans la clause where
// pour la vérification optimiste.
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();
}
L'exemple montre que l'objet LogElement est utilisé pour obtenir la valeur de la version initiale. Lorsque la transaction accède pour la première fois à l'entrée de mappe, un objet LogElement est créé avec l'objet Employee initial obtenu à partir de la mappe. L'objet Employee initial est également transmis à la méthode getVersionedObjectForValue dans l'interface OptimisticCallback et le résultat est enregistré dans l'objet LogElement. Ce traitement a lieu avant qu'une application soit référencée à l'objet Employee initial et qu'elle appelle une méthode modifiant l'état de l'objet Employee initial.
L'exemple montre que le chargeur utilise la méthode getVersiondObjectForValue pour obtenir l'objet de version pour l'objet Employee mis à jour. Avant d'appeler la méthode batchUpdate dans l'interface Loader, eXtreme Scale appelle la méthode updateVersionedObjectForValue dans l'interface OptimisticCallback pour provoquer la génération d'un nouvel objet de version pour l'objet Employee mis à jour. Une fois que la méthode batchUpdate est renvoyée à l'ObjectGrid, l'objet LogElement est mis à jour avec l'objet de version actuelle et devient le nouvel objet de version initiale. Cette étape est nécessaire parce qu'il se peut que l'application ait appelé la méthode flush sur la mappe et non la méthode commit sur la session. Il est possible que le chargeur soit appelé plusieurs fois par une seule transaction pour la même clé. C'est pourquoi, eXtreme Scale fait en sorte que l'objet LogElement soit mis à jour avec l'objet de nouvelle version à chaque mise à jour de la ligne dans la table employee.
Désormais que le chargeur détient l'objet de version initiale et l'objet de prochaine version, il peut exécuter une instruction SQL update qui définit la colonne SEQNO sur la valeur de l'objet de prochaine version et utilise cette dernière dans la clause where. Cette approche est parfois désignée comme étant une instruction update sur-qualifiée. L'utilisation d'une instruction update sur-qualifiée permet à la base de données relationnelle de vérifier que la ligne n'a pas été modifiée par une autre transaction entre la lecture des données de la base de données par cette transaction et la mise à jour de la base de données par cette transaction. Si une autre transaction a modifié la ligne, le tableau de comptage renvoyé par la mise à jour par lots indique qu'aucune ligne n'a été mise à jour pour cette clé. Le chargeur est chargé de vérifier que l'opération SQL update n'a pas mis à jour la ligne. Si c'est le cas, il affiche une exception com.ibm.websphere.objectgrid.plugins.OptimisticCollisionException pour informer la session que la méthode batchUpdate a échoué en raison de la tentative de plusieurs transactions simultanées de mettre à jour la même ligne dans la table de base de données. Cette exception provoque l'annulation de la session et l'application doit relancer la transaction entière. La justification réside dans le fait que la nouvelle tentative aboutira, ce qui explique pourquoi cette approche est appelée optimiste. L'approche optimiste est plus performante si les données sont peu modifiées ou si les transactions simultanées tentent rarement de mettre à jour la même ligne.
Le chargeur doit impérativement utiliser le paramètre de clé du constructeur OptimisticCollisionException pour identifier quelle clé ou quel ensemble de clés a provoqué l'échec de la méthode optimiste batchUpdate. Le paramètre de clé peut être l'objet clé proprement dit ou un tableau d'objets clé si plusieurs clés ont abouti à l'échec de la mise à jour optimiste. eXtreme Scale utilise la méthode getKey du constructeur OptimisticCollisionException pour déterminer quelles entrées de mappe contiennent les données périmées et ont provoqué l'exception. Un aspect du processus d'annulation consiste à expulser les entrées de mappe périmées de la mappe. Cette expulsion s'avère nécessaire pour que toutes les transactions suivantes accédant aux mêmes clés déclenchent la méthode get de l'interface Loader appelée pour actualiser les entrées de mappe avec les données actuelles de la base de données.