Criando um Utilitário de Carga

É possível gravar sua própria implementação de plug-in de utilitário de carga em seus aplicativos, que deve seguir as convenções de plug-in do WebSphere eXtreme Scale comuns.

Incluindo um Plug-in do Utilitário de Carga

A interface do Utilitário de Carga possui a seguinte definição:

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;
}

Consulte o Utilitários de Carga para obter informações adicionais.

Método get

O mapa de apoio chama o método get do utilitário de carga para obter os valores associados a uma lista de chaves, que é transmitida como o argumento keyList. O método get é necessário para retornar uma lista de valores java.lang.util.List, um valor para cada chave que estiver na lista de chaves. O primeiro valor retornado na lista de valores corresponde à primeira chave na lista de chaves, o segundo valor retornado na lista de valores corresponde à segunda chave na lista de chaves e assim por diante. Se o utilitário de carga não localizar o valor para uma chave na lista de chaves, ele precisará retornar o objeto de valor especial KEY_NOT_FOUND definido na interface do Utilitário de Carga. Como um mapa de apoio pode ser configurado para permitir null como um valor válido, é muito importante para o utilitário de carga retornar o objeto especial KEY_NOT_FOUND quando ele não puder localizar a chave. Este valor especial permite que o mapa de apoio faça a distinção entre um valor null e um valor inexistente porque a chave não foi localizada. Se um mapa de suporte não suportar valores null, um utilitário de carga que retorna um valor nulo em vez do objeto KEY_NOT_FOUND para uma chave que não existe resultará em uma exceção.

O argumento forUpdate informa o utilitário de carga se o aplicativo chamou um método get no mapa ou um método getForUpdate no mapa. Consulte a Interface ObjectMap para obter mais informações. O Loader é responsável por implementar uma política de controle de simultaneidade que controla o acesso simultâneo ao armazenamento persistente. Por exemplo, muitos sistemas de gerenciamento de banco de dados relacional suportam a sintaxe for update na instrução SQL select utilizada para ler dados a partir de uma tabela relacional. O utilitário de carga pode optar por utilizar a sintaxe for update na instrução SQL select com base se um true booleano foi transmitido como o valor de argumento para o parâmetro forUpdate deste método. Geralmente, o Utilitário de Carga utiliza a sintaxe for update apenas quando a política de controle de simultaneidade pessimista for utilizada. Para um controle de simultaneidade otimista, o Utilitário de Carga nunca utiliza a sintaxe for update na instrução SQL select. O utilitário de carga é responsável por decidir utilizar o argumento forUpdate com base na política de controle de simultaneidade que está sendo utilizada pelo utilitário de carga.

Para obter uma explicação do parâmetro txid, consulte Plug-ins para o Gerenciamento de Eventos de Ciclo de Vida da Transação.

Método batchUpdate

O método batchUpdate é importante na interface Loader. Este método é chamado sempre que o eXtreme Scale precisa aplicar todas as alterações atuais no Utilitário de Carga. O Utilitário de Carga recebe uma lista de alterações para o Mapa selecionado. As alterações são iteradas e aplicadas ao backend. O método recebe o valor TxID atual e as alterações a serem aplicadas. A amostra a seguir interage sobre o conjunto de alterações e três instruções JDBC (Java Database Connectivity) em lote, uma com insert, outra com update e uma com 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 {
        // Obter uma conexão SQL para utilizar.
        Connection conn = getConnection(tx);
        try {
            // Processar a lista de alterações e construir um conjunto de instruções preparadas
            // para executar uma operação SQL update, insert ou delete
            // de batch.
            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;
                }
            }
            // Executar as instruções de batch que foram construídas pelo loop acima.
            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;
        }
    }
A amostra anterior ilustra a lógica de alto nível de processamento do argumento LogSequence, mas os detalhes de como uma instrução SQL insert, update ou delete é construída não são ilustrados. Alguns dos pontos-chave que estão ilustrados incluem:
  • O método getPendingChanges é chamado no argumento LogSequence para obter um iterador sobre a lista de LogElements que o Utilitário de Carga precisa processar.
  • O método LogElement.getType().getCode() é utilizado para determinar se o LogElement serve para uma operação SQL insert, update ou delete.
  • Uma exceção SQLException é capturada e encadeada em uma exceção LoaderException exibida para reportar que ocorreu uma exceção durante a atualização do batch.
  • O suporte à atualização do batch JDBC é utilizado para reduzir o número de consultas para o backend que devem ser feitas.

Método preloadMap

Durante a inicialização do eXtreme Scale, cada mapa de apoio que é definido é inicializado. Se um Utilitário de Carga for conectado a um mapa de apoio, o mapa de apoio chamará o método preloadMap na interface do Utilitário de Carga para permitir que o utilitário de carga faça a pré-busca de dados de seu backend e carregue os dados no mapa. A amostra a seguir assume que as primeiras 100 linhas de uma tabela Employee são lidas a partir do banco de dados e carregadas no mapa. A classe EmployeeRecord é uma classe fornecida pelo aplicativo que contém os dados de funcionários lidos a partir da tabela de funcionários.

Nota: Essa amostra busca todos os dados do banco de dados e depois os insere no mapa base de uma partição. Em um cenário de implementação do eXtreme Scale distribuído real, os dados devem ser distribuídos em todas as partições. Consulte Desenvolvendo Carregadores JPA Baseados em Cliente para obter informações adicionais.
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();
            // Obter uma conexão de autoconfirmação para utilização que esteja configurada para
            // um nível de isolamento confirmado por leitura.
            conn = getAutoCommitConnection(tx);
            // Pré-carregar o Mapa Employee com objetos EmployeeRecord.
            // Ler todos os Funcionários a partir da tabela, mas
            // limitar o pré-carregamento às primeiras 100 linhas.
            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;
            }
            // Confirmar a transação.
            session.commit();
            tranActive = false;
        } catch (Throwable t) {
            throw new LoaderException("preload failure: " + t, t);
        } finally {
            if (tranActive) {
                try {
                    session.rollback();
                } catch (Throwable t2) {
                    // Tolerar falhas de rollback e
                    // permitir que o Throwable original seja emitido.
                }
            }
            // Certificar-se de limpar outros recursos do banco de dados aqui,
            // bem como instruções de fechamento, conjuntos de resultados, etc.
        }
    }
Esta amostra ilustra os seguintes pontos-chave:
  • O mapa de suporte preloadMap utiliza o objeto Session transmitido para ele como o argumento de sessão.
  • O método Session.beginNoWriteThrough é utilizado para iniciar a transação em vez do método begin.
  • O Utilitário de Carga não pode ser chamado para cada operação put que ocorrer neste método para carregar o mapa.
  • O utilitário de carga pode mapear colunas da tabela de funcionários em um campo no objeto Java EmployeeRecord. O Utilitário de Carga captura todas as exceções que podem ser emitidas que ocorrem e emite uma exceção LoaderException com a exceção que pode ser emitida capturada encadeada a ele.
  • O bloco finally assegura que qualquer exceção que possa ser emitida, e que ocorre entre o tempo em que os métodos beginNoWriteThrough e commit são chamados, faça o bloco finally recuperar a transação ativa. Esta ação é importante para assegurar que qualquer transação que tenha sido iniciada pelo método preloadMap seja concluída antes de retornar ao responsável pela chamada. O bloco finally é um bom local para você executar outras ações de limpeza que podem ser necessárias, como o fechamento da conexão Java Database Connectivity (JDBC) e de outros objetos JDBC.

A amostra preloadMap está utilizando uma instrução SQL select que seleciona todas as linhas da tabela. Em seu Utilitário de Carga fornecido pelo aplicativo, pode ser necessário configurar uma ou mais propriedades do Utilitário de Carga para controlar a quantidade da tabela que precisa ser pré-carregada no mapa.

Como o método preloadMap é chamado apenas uma vez durante a inicialização de BackingMap, ele também é um bom local para executar o código de inicialização do Utilitário de Carga em uma etapa. Mesmo que o Utilitário de Carga opte por não fazer a pré-busca de dados do backend e carregar os dados no mapa, provavelmente, ele precisará desempenhar alguma outra inicialização em uma etapa para tornar outros métodos do Utilitário de Carga mais eficientes. O exemplo a seguir ilustra o armazenamento em cache do objeto TransactionCallback e do objeto OptimisticCallback como variáveis da instância do Utilitário de Carga para que os outros métodos do Utilitário de Carga não precisem fazer chamadas de método para obter acesso a estes objetos. Este armazenamento em cache de valores de plug-in do ObjectGrid pode ser desempenhado pois, após a inicialização do BackingMap, os objetos TransactionCallback e OptimisticCallback não podem ser alterados ou substituídos. É aceitável armazenar em cache estas referências do objeto como variáveis da instância do Loader.

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;

    // Variáveis da instância do Loader.
    MyTransactionCallback ivTcb; // MyTransactionCallback

    // estende TransactionCallback
    MyOptimisticCallback ivOcb; // MyOptimisticCallback

    // implementa OptimisticCallback
    // ...
    public void preloadMap(Session session, BackingMap backingMap) throws LoaderException [Replication programming]
        // Armazenar em cache os objetos TransactionCallback e OptimisticCallback
        // em variáveis da instância deste Loader.
        ivTcb = (MyTransactionCallback) session.getObjectGrid().getTransactionCallback();
        ivOcb = (MyOptimisticCallback) backingMap.getOptimisticCallback();
        // O restante do código preloadMap (conforme mostrado no exemplo anterior).
    }

Para obter informações sobre o pré-carregamento e o pré-carregamento recuperável pertencentes ao failover de replicação, consulte o Replicação para Disponibilidadeinformações sobre a replicação no Visão Geral do Produto.

Utilitários de Carga com Mapas de Entidade

Se o utilitário de carga for conectado a um mapa de entidade, o utilitário de carga deverá lidar com os objetos de tupla. Os objetos de tupla são um formato de dados de entidade especial. O utilitário de carga deve converter os dados entre a tupla e outros formatos de dados. Por exemplo, o método get retorna uma lista de valores que correspondem ao conjunto de chaves que são transmitidas para o método. As chaves transmitidas estão no tipo de Tupla, chamadas de tuplas de chaves. Assumindo que o utilitário de carga mantém o mapa com um banco de dados utilizando JDBC, o método get deve converter cada tupla de chave em uma lista de valores de atributos que corresponda às colunas de chaves primárias da tabela que é mapeada para o mapa de entidade, executar a instrução SELECT com a cláusula WHERE que utiliza os valores de atributos convertidos como critérios para procurar dados no banco de dados e, em seguida, converter os dados retornados em tuplas de valores. O método get obtém dados do banco de dados e converte os dados em tuplas de valores para as tuplas de chaves transmitidas e, em seguida, retorna uma lista de tuplas de valores que corresponde ao conjunto de chaves de tuplas que são transmitidas para o responsável pela chamada. O método get pode executar uma instrução SELECT para procurar todos os dados de uma vez ou executar uma instrução SELECT para cada tupla de chave. Para obter detalhes sobre a programação que mostra como usar o utilitário de carga quando os dados são armazenados usando um gerenciador de entidades, consulte Utilizando um Utilitário de Carga com Mapas de Entidade e Tuplas.