Ecriture d'un chargeur

Vous pouvez écrire votre implémentation de plug-in Loader dans vos applications qui doit suivre les conventions de plug-in WebSphere eXtreme Scale communes.

Ajout d'un plug-in Loader

L'interface du chargeur comporte la définition suivante :

public interface Loader
{
    static final SpecialValue KEY_NOT_FOUND;
    List get(TxID txid, List keyList, boolean forUpdate) throws LoaderException;
    void batchUpdate(TxID txid, LogSequence sequence) throws
				LoaderException, OptimisticCollisionException;
    void preloadMap(Session session, BackingMap backingMap) throws LoaderException;
}

Pour plus d'informations, voir Chargeurs.

Méthode get

La mappe de sauvegarde appelle la méthode get du chargeur pour obtenir les valeurs associées à une liste de clés transmise en tant qu'argument keyList. La méthode get est requise pour renvoyer une liste de valeurs java.lang.util.List, une valeur pour chaque clé contenue dans la liste de clés. La première valeur renvoyée dans la liste de valeurs correspond à la première clé de la liste de clés, la deuxième valeur renvoyée dans la liste de valeurs correspond à la deuxième clé de la liste de clés et ainsi de suite. Si le chargeur ne trouve pas la valeur pour une clé de la liste de clés, il est requis pour renvoyer l'objet de valeur spécial KEY_NOT_FOUND défini dans l'interface du chargeur. Etant donné qu'une mappe de sauvegarde peut être configurée pour autoriser la valeur NULL comme étant une valeur valide, il est crucial pour le chargeur de renvoyer l'objet spécial KEY_NOT_FOUND lorsque la clé ne peut pas être détectée. Cette valeur spéciale permet à la mappe de sauvegarde de distinguer entre une valeur NULL et une valeur inexistante lorsque la clé est introuvable. Si une mappe de sauvegarde ne prend pas en charge les valeurs NULL, un chargeur renvoyant une valeur NULL et non l'objet KEY_NOT_FOUND pour une clé inexistante déclenche une exception.

L'argument forUpdate informe le chargeur si l'application a appelé une méthode get sur la mappe ou une méthode getForUpdate sur la mappe. Pour plus d'informations, reportez-vous à l' interface ObjectMap. Le chargeur est responsable de l'implémentation d'une stratégie de contrôle des accès simultanés au stockage de persistance. Par exemple, un grand nombre de systèmes de gestion de base de données relationnelle prennent en charge la syntaxe for update sur l'instruction SQL select permettant de lire les données à partir d'une table relationnelle. Le chargeur peut choisir d'utiliser la syntaxe for update sur l'instruction SQL select en fonction de la transmission ou non de la valeur boolean true en que valeur d'argument pour le paramètre forUpdate de cette méthode. Le chargeur utilise généralement la syntaxe for update uniquement lorsque la stratégie de contrôle des accès simultanés pessimiste est mise en oeuvre. Dans le cas d'une stratégie de contrôle des accès simultanés optimiste, le chargeur n'utilise jamais la syntaxe for update sur l'instruction SQL select. Le chargeur décide d'utiliser l'argument forUpdate en fonction de la stratégie de contrôle des accès simultanés mise en oeuvre par le chargeur.

Pour obtenir une explication du paramètre txid, reportez-vous à la section Plug-in de gestion des événements du cycle de vie des transactions.

Méthode batchUpdate

La méthode batchUpdate est importante dans l'interface Loader. Cette méthode est appelée dès que eXtreme Scale doit appliquer toutes les modifications en cours au chargeur. Le chargeur obtient une liste des modifications pour la mappe sélectionnée. Les modifications sont itérées et appliquées au programme d'arrière plan. La méthode reçoit la valeur TxID et les modifications à appliquer. L'exemple ci-dessous parcourt l'ensemble des modifications et traite par lots trois instructions JDBC (Java Database Connectivity), une avec insert, l'autre avec update et la dernière avec delete.

import java.util.Collection;
import java.util.Map;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.ibm.websphere.objectgrid.TxID;
import com.ibm.websphere.objectgrid.plugins.Loader;
import com.ibm.websphere.objectgrid.plugins.LoaderException;
import com.ibm.websphere.objectgrid.plugins.LogElement;
import com.ibm.websphere.objectgrid.plugins.LogSequence;

    public void batchUpdate(TxID tx, LogSequence sequence) throws LoaderException {
        // Etablissez une connexion SQL à utiliser.
        Connection conn = getConnection(tx);
        try {
            // Traitez la liste des modifications et créez un ensemble d'instructions
            // préparées pour l'exécution d'une opération SQL par lots update, insert
            // ou delete.
            Iterator iter = sequence.getPendingChanges();
            while (iter.hasNext()) {
                LogElement logElement = (LogElement) iter.next();
                Object key = logElement.getKey();
                Object value = logElement.getCurrentValue();
                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;
                }
            }
            // Exécutez les instructions par lots créées par la boucle au-dessus.
            Collection statements = getPreparedStatementCollection(tx, conn);
            iter = statements.iterator();
            while (iter.hasNext()) {
                PreparedStatement pstmt = (PreparedStatement) iter.next();
                pstmt.executeBatch();
            }
        } catch (SQLException e) {
            LoaderException ex = new LoaderException(e);
            throw ex;
        }
    }
L'exemple précédent illustre la logique de niveau supérieur du traitement de l'argument LogSequence mais les détails de la création d'une instruction SQL insert, update ou delete ne sont pas illustrés. Les principaux points illustrés sont les suivants :
  • La méthode getPendingChanges est appelée sur l'argument LogSequence pour obtenir un itérateur sur la liste de LogElements que le chargeur doit traiter.
  • La méthode LogElement.getType().getCode() est utilisée pour déterminer si le LogElement correspond à une opération SQL insert, update ou delete.
  • Une exception SQLException est émise et est chaînée à une exception LoaderException qui signale qu'une exception est survenue lors de la mise à jour par lots.
  • La prise en charge de la mise à jour par lots JDBC permet de réduire le nombre de requêtes devant être adressées au serveur principal.

Méthode preloadMap

Pendant l'initialisation d'eXtreme Scale , chaque mappe de sauvegarde définie est initialisée. Si un chargeur est relié à une mappe de sauvegarde, cette dernière appelle la méthode preloadMap dans l'interface Loader pour permettre au chargeur de préextraire des données à partir du serveur principal et de les charger dans la mappe. D'après l'exemple suivant, les 100 premières lignes de la table Employee sont lues depuis la base de données et sont chargées dans la mappe. La classe EmployeeRecord est une classe fournie par l'application contenant les données relatives à l'employé lues dans la table employee.

Remarque : Cet exemple extrait toutes les données de la base de données, puis les insère dans la mappe de base d'une partition. Dans un scénario de déploiement réparti eXtreme Scale concret, les données doivent être réparties entre toutes les partitions. Voir Développement des chargeurs JPA basés sur le client pour plus d'informations.
import java.sql.PreparedStatement;
import java.sql.SQLException;
import com.ibm.websphere.objectgrid.Session;
import com.ibm.websphere.objectgrid.TxID;
import com.ibm.websphere.objectgrid.plugins.Loader;
import com.ibm.websphere.objectgrid.plugins.LoaderException

    public void preloadMap(Session session, BackingMap backingMap) throws LoaderException {
        boolean tranActive = false;
        ResultSet results = null;
        Statement stmt = null;
        Connection conn = null;
        try {
            session.beginNoWriteThrough();
            tranActive = true;
            ObjectMap map = session.getMap(backingMap.getName());
            TxID tx = session.getTxID();
            // Etablissez une connexion de validation automatique définie sur
            // un niveau d'isolement lecture validée.
            conn = getAutoCommitConnection(tx);
            // Préchargez la mappe de l'employé avec les objets
            // EmployeeRecord. Lisez tous les employés de la table mais
            // limitez le préchargement aux 100 premières lignes.
            stmt = conn.createStatement();
            results = stmt.executeQuery(SELECT_ALL);
            int rows = 0;
            while (results.next() && rows < 100) {
                int key = results.getInt(EMPNO_INDEX);
                EmployeeRecord emp = new EmployeeRecord(key);
                emp.setLastName(results.getString(LASTNAME_INDEX));
                emp.setFirstName(results.getString(FIRSTNAME_INDEX));
                emp.setDepartmentName(results.getString(DEPTNAME_INDEX));
                emp.updateSequenceNumber(results.getLong(SEQNO_INDEX));
                emp.setManagerNumber(results.getInt(MGRNO_INDEX));
                map.put(new Integer(key), emp);
                ++rows;
            }
            // Validez la transaction.
            session.commit();
            tranActive = false;
        } catch (Throwable t) {
            throw new LoaderException("preload failure: " + t, t);
        } finally {
            if (tranActive) {
                try {
                    session.rollback();
                } catch (Throwable t2) {
                    // Tolérez tous les échecs d'annulation et
                    // autorisez l'émission de la classe throwable originale.
                }
            }
            // Assurez-vous de nettoyer ici les ressources des autres bases de données
            // telles que les instructions de clôture, les ensembles de résultats, etc.
        }
    }
Ce modèle illustre les principaux points suivants :
  • La mappe de sauvegarde preloadMap utilise l'objet Session qui lui est transmis en tant qu'argument session.
  • La méthode Session.beginNoWriteThrough sert à commencer la transaction à la place de la méthode begin.
  • Le chargeur ne peut pas être appelé pour chaque opération put qui se produit dans cette méthode pour le chargement de la mappe.
  • Le chargeur peut mapper des colonnes de la table de l'employé sur une zone de l'objet Java EmployeeRecord. Il intercepte toutes les exceptions de la classe throwable déclenchées et émet une exception LoaderException chaînée avec l'exception de la classe throwable interceptée.
  • Le bloc finally veille à ce que que toutes les exceptions de la classe throwable qui sont émises entre l'appel de la méthode beginNoWriteThrough et celui de la méthode commit déclenchent l'annulation de la transaction active. Cette action s'avère cruciale pour que toutes les transactions démarrées par la méthode preloadMap soient terminées avant d'être envoyées au demandeur. Le bloc finally est l'emplacement idéal pour effectuer d'autres actions de nettoyage, notamment la fermeture de la connexion JDBC (Java Database Connectivity) et des autres objets JDBC.

L'exemple preloadMap utilise une instruction SQL Select qui sélectionne toutes les lignes de la table. A partir du chargeur de l'application, il est conseillé de définir une ou plusieurs propriétés du chargeur pour contrôler le degré de préchargement de la table dans la mappe.

Etant donné que la méthode preloadMap n'est appelée qu'une seule fois pendant l'initialisation de la mappe de sauvegarde, il s'agit également d'un bon emplacement pour exécuter le code d'initialisation unique du chargeur. Même si un chargeur choisit de ne pas préextraire de données du serveur principal et de charger les données dans la mappe, il doit probablement procéder à certaines initialisations uniques pour renforcer l'efficacité d'autres méthodes du chargeur. L'exemple suivant illustre la mise en cache des objets TransactionCallback et OptimisticCallback en tant que variables d'instance du chargeur de façon à dispenser les autres méthodes du chargeur d'effectuer des appels de méthode pour accéder à ces objets. Cette mise en cache des valeurs du plug-in ObjectGrid peut être réalisée car les objets TransactionCallback et OptimisticCallback ne peuvent être ni modifiés ni remplacés après l'initialisation de la mappe de sauvegarde. Il est acceptable de mettre en cache ces références d'objet en tant que variables d'instance du chargeur.

import com.ibm.websphere.objectgrid.Session;
import com.ibm.websphere.objectgrid.BackingMap;
import com.ibm.websphere.objectgrid.plugins.OptimisticCallback;
import com.ibm.websphere.objectgrid.plugins.TransactionCallback;

    // Variables d'instance du chargeur.
    MyTransactionCallback ivTcb; // MyTransactionCallback

    // étend TransactionCallback
    MyOptimisticCallback ivOcb; // MyOptimisticCallback

    // implémente OptimisticCallback
    // ...
    public void preloadMap(Session session, BackingMap backingMap) throws LoaderException
		 [Replication programming]
        // Mettre en cache les objets TransactionCallback et OptimisticCallback
        // dans les variables d'instance de ce chargeur.
        ivTcb = (MyTransactionCallback) session.getObjectGrid().getTransactionCallback();
        ivOcb = (MyOptimisticCallback) backingMap.getOptimisticCallback();
        // Le reste du code preloadMap (tel qu'indiqué dans l'exemple précédent).
    }

Pour plus d'informations sur le préchargement et le préchargement récupérable dans le cadre du basculement de réplication, voir Réplication à des fins de disponibilitéles informations sur la réplication dans Présentation du produit.

Chargeurs avec mappes d'entités

Si le chargeur est relié à une mappe d'entité, il doit traiter des objets de bloc de données. Les objets de bloc de données correspondent à un format de données d'entité spécial. Le chargeur doit convertir les données entre le format de bloc de données et les autres formats de données. Par exemple, la méthode get renvoie une liste de valeurs qui correspond à l'ensemble des clés qui sont transmises à la méthode. Les clés transmises sont de type Bloc de données, à savoir des bloc de données de clés. En supposant que le chargeur conserve la mappe avec une base de données JDBC, la méthode get doit convertir chaque bloc de données de clés en une liste de valeurs d'attribut qui correspondent aux colonnes de la clé primaire de la table mappée sur la mappe d'entité, exécuter l'instruction SELECT avec la clause WHERE qui utilise les valeurs d'attribut converties comme critère d'extraction des données de la base de données, puis convertir les données renvoyées en blocs de données de valeurs. La méthode get extrait les données de la base de données et les convertit en blocs de données de valeurs pour les blocs de données de clés transmis, puis renvoie une liste des blocs de données de valeurs correspondant à l'ensemble des clés de bloc de données transmises au demandeur. La méthode get peut exécuter une instruction SELECT pour extraire toutes les données en même temps ou pour chaque bloc de données de clés. Pour obtenir des détails de programmation illustrant l'utilisation du chargeur lors du stockage des données à l'aide d'un gestionnaire d'entités, reportez-vous à la rubrique Utilisation d'un chargeur avec des mappes d'entité et des tuples.