Os utilitários de carga são plug-ins de mapa de apoio que são chamados quando são feitas alterações no mapa de apoio ou quando o mapa de apoio não pode atender a um pedido de dados (um erro de cache).
Os utilitários de carga são plug-ins de mapa de apoio que são chamados quando são feitas alterações no mapa de apoio ou quando o mapa de apoio não pode atender a um pedido de dados (um erro de cache). Para obter uma visão geral de como o eXtreme Scale interage com um carregador, consulte o Cache Sequencial.
Cada mapa de apoio tem um atributo preloadMode booleano que é configurado para indicar se o pré-carregamento de um mapa é executado de forma assíncrona. Por padrão, o atributo preloadMode está configurado como false, o que indica que a inicialização do mapa de suporte não será concluída até que o pré-carregamento do mapa esteja concluído. Por exemplo, a inicialização do mapa de apoio não será concluída até que o método preloadMap seja retornado. Se o método preloadMap ler uma grande quantidade de dados no seu back end e carregá-los para o mapa, o tempo de conclusão desse procedimento pode ser relativamente longo. Neste caso, é possível configurar um mapa de suporte para utilizar o pré-carregamento assíncrono do mapa, configurando o atributo preloadMode como true. Essa configuração faz com que o código de inicialização do mapa de apoio inicie um encadeamento que chama o método preloadMap, permitindo que a inicialização de um mapa de apoio seja concluída enquanto o pré-carregamento do mapa ainda está em andamento.
Em um cenário eXtreme Scale distribuído, um dos padrões de pré-carregamento é o pré-carregamento de cliente. No padrão de pré-carregamento do cliente, um cliente eXtreme Scale é responsável por recuperar dados do backend e, em seguida, inserir os dados no servidor de contêiner distribuído usando agentes DataGrid. Além disso, o pré-carregamento de cliente poderia ser executado no método Loader.preloadMap em uma, e apenas uma, partição específica. Nesse caso, o carregamento assíncrono de dados para a grade se tornaria muito importante. Se o pré-carregamento do cliente fosse executado no mesmo encadeamento, o mapa de apoio nunca seria inicializado, assim, a partição na qual ele reside nunca ficaria ON-LINE. Portanto, o cliente eXtreme Scale não poderia enviar o pedido para a partição e, eventualmente, isso causaria uma exceção.
Se um cliente do eXtreme Scale for usado no método preloadMap, você deverá definir o atributo preloadMode como true. A alternativa é iniciar um encadeamento no código de pré-carregamento do cliente.
O trecho de código a seguir ilustra como o atributo preloadMode é configurado para ativar o pré-carregamento assíncrono:
BackingMap bm = og.defineMap( "map1" );
bm.setPreloadMode( true );
O atributo preloadMode também pode ser configurado utilizando um arquivo XML conforme ilustrado no seguinte exemplo:
<backingMap name="map1" preloadMode="true"
pluginCollectionRef="map1" lockStrategy="OPTIMISTIC" />
Tanto o método get quanto o método batchUpdate na interface do Loader são transmitidos para um objeto TxID que representa a transação Session que requer que a operação get ou batchUpdate seja executada. Os métodos get e batchUpdate podem ser chamados mais de uma vez por transação. Portanto, os objetos com escopo definido pela transação requeridos pelo Loader geralmente são mantidos em um slot do objeto TxID. Um utilitário de carga JDBC (Java Database Connectivity) é usado para ilustrar como um utilitário de carga usa as interfaces TxID e TransactionCallback.
Vários mapas do ObjectGrid podem ser armazenados no mesmo banco de dados. Cada mapa possui seu próprio carregador, e cada carregador pode precisar se conectar ao mesmo banco de dados. Quando os carregadores se conectam com o banco de dados, eles devem usar a mesma conexão JDBC. Usar a mesma conexão confirma as mudanças em cada tabela como parte da mesma transação do banco de dados. Geralmente, a mesma pessoa que grava a implementação Loader também grava a implementação TransactionCallback. O melhor método é quando a interface TransactionCallback é estendida para incluir os métodos que o Loader precisa para obter uma conexão com o banco de dados e para armazenar em cache as instruções preparadas. O motivo para esta metodologia torna-se aparente à medida que você visualiza como as interfaces TransactionCallback e TxID são utilizadas pelo utilitário de carga.
Como um exemplo, o utilitário de carga pode precisar da interface TransactionCallback para ser estendido conforme a seguir:
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 );
}
Com tais novos métodos, os métodos get e batchUpdate do Loader podem obter uma conexão da seguinte forma:
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;
}
No exemplo anterior e nos exemplos que seguem, vTcb e ivOcb são variáveis da instância do Carregador que foram inicializadas conforme descrito na seção Considerações de Pré-carregamento. A variável ivTcb é uma referência à instância MyTransactionCallback e ivOcb é uma referência à instância MyOptimisticCallback. A variável databaseName é uma variável da instância do Utilitário de Carga que foi configurada como uma propriedade do Utilitário de Carga durante a inicialização do mapa de suporte. O argumento isolationLevel é uma das constantes da Conexão JDBC que estão definidas para os diversos níveis de isolamento suportados pelo JDBC. Se o Utilitário de Carga estiver utilizando uma implementação otimista, o método get geralmente utilizará uma conexão de autoconfirmação JDBC para buscar os dados do banco de dados. Nesse caso, o Utilitário de Carga pode ter um método getAutoCommitConnection que seja implementado da seguinte forma:
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;
}
Lembre-se de que o método batchUpdate possui a seguinte instrução switch:
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;
}
Cada um dos métodos buildBatchSQL utiliza a interface MyTransactionCallback para obter uma instrução preparada. Este é um trecho de código que mostra o método buildBatchSQLUpdate construindo uma instrução SQL update para atualizar uma entrada EmployeeRecord e incluindo-a na atualização de batch:
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();
}
Quando o loop batchUpdate tiver construído todas as instruções preparadas, ele chamará o método getPreparedStatementCollection. Esse método é implementado como segue:
private Collection getPreparedStatementCollection( TxID tx, Connection conn )
{
return ( ivTcb.getPreparedStatementCollection( tx, conn, "employee" ) );
}
Conforme mencionado anteriormente, o Utilitário de Carga pode utilizar uma abordagem otimista para controle de simultaneidade. Nesse caso, o exemplo do método buildBatchSQLUpdate precisará ser modificado ligeiramente para implementar uma abordagem otimista. Existem várias maneiras possíveis para utilizar uma abordagem otimista. Uma maneira típica é ter uma coluna de time stamp ou uma coluna do contador de números de sequência para o controle de versões de cada atualização da linha. Suponha que a tabela de funcionários tenha uma coluna de números de sequência que aumenta sempre que a linha é atualizada. Em seguida, você modifica a assinatura do método buildBatchSQLUpdate para que ela seja transmitida para o objeto LogElement em vez do par chave e valor. Ele também precisa utilizar o objeto OptimisticCallback que está conectado ao mapa de suporte para obter o objeto da versão inicial e para atualizar o objeto da versão. Este é um exemplo de método buildBatchSQLUpdate modificado que utiliza a variável da instância ivOcb inicializada conforme descrito na seção preloadMap:
exemplo de código do método batch-update modificado
private void buildBatchSQLUpdate( TxID tx, LogElement le, Connection conn )
throws SQLException, LoaderException
{
// Obter o objeto da versão inicial quando esta entrada do mapa foi lida pela última vez ou
// atualizada no banco de dados.
Employee emp = (Employee) le.getCurrentValue();
long initialVersion = ((Long) le.getVersionedValue()).longValue();
// Obter o objeto da versão de Employee atualizado para a operação SQL
//update.
Long currentVersion = (Long)ivOcb.getVersionedObjectForValue( emp );
long nextVersion = currentVersion.longValue();
// Agora construa o SQL update que inclui o objeto de versão na cláusula where
// para verificação otimista.
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();
}
O exemplo mostra que o LogElement é utilizado para obter o valor de versão inicial. Quando a transação acessa pela primeira vez a entrada do mapa, é criado um LogElement com o objeto Employee inicial obtido do mapa. O objeto Employee inicial também é transmitido para o método getVersionedObjectForValue na interface OptimisticCallback e o resultado é salvo no LogElement. Este processamento ocorre antes de um aplicativo receber uma referência ao objeto Employee inicial e de chamar algum método que altere o estado do objeto Employee inicial.
O exemplo mostra que o Loader utiliza o método getVersiondObjectForValue para obter o objeto de versão para o objeto Employee atual atualizado. Antes de chamar o método batchUpdate na interface do utilitário de carga, eXtreme Scale chama o método updateVersionedObjectForValue na interface OptimisticCallback para gerar um novo objeto de versão a ser gerado para o objeto Employee atualizado. Quando o método batchUpdate retornar ao ObjectGrid, o LogElement será atualizado com o objeto de versão atual e se tornará o novo objeto de versão inicial. Esta etapa é necessária porque o aplicativo pode ter chamado o método flush no mapa em vez do método commit na Session. É possível que o Loader seja chamado várias vezes por uma única transação para a mesma chave. Por este motivo, o eXtreme Scale assegura que o LogElement seja atualizado com o novo objeto de versão toda vez que a linha for atualizada na tabela de funcionários.
Agora que o Utilitário de Carga tem o objeto de versão inicial e o próximo objeto de versão, ele pode executar uma instrução SQL update que configura a coluna SEQNO para o próximo valor do objeto de versão e utiliza o valor do objeto de versão inicial na cláusula where. Essa abordagem, às vezes, é referida como uma instrução update super qualificada. A utilização da instrução update super qualificada permite que o banco de dados relacional verifique se a linha não foi alterada por alguma outra transação entre o tempo de leitura do banco de dados por parte da transação e o momento em que esta o atualizou. Se outra transação modificou a linha, a matriz de contagem retornada pela atualização de batch indica que zero linhas foram atualizadas para esta chave. O Utilitário de Carga é responsável por verificar se a operação SQL update atualizou a linha. Se isso não ocorreu, ele exibirá uma exceção com.ibm.websphere.objectgrid.plugins.OptimisticCollisionException para informar o objeto Session que o método batchUpdate falhou porque mais de uma transação simultânea está tentando atualizar a mesma linha na tabela de banco de dados. Esta exceção faz a Session efetuar rollback e o aplicativo deve tentar novamente a transação inteira. O fundamento lógico é que a nova tentativa será bem-sucedida, motivo pelo qual esta abordagem é chamada de otimista. A abordagem otimista apresenta um desempenho melhor quando os dados não sofrem alterações frequentes ou transações simultâneas raramente tentam atualizar a mesma linha.
É importante que o Utilitário de Carga utilize o parâmetro key do construtor OptimisticCollisionException para identificar qual chave ou conjunto de chaves causou a falha do método batchUpdate otimista. O parâmetro de chave pode ser o próprio objeto de chave ou uma matriz de objetos de chave se mais de uma chave resultar em uma falha de atualização otimista. E o eXtreme Scale usa o método getKey do construtor OptimisticCollisionException para determinar quais entradas de mapa contêm dados desatualizados e causaram a exceção. Parte do processamento de rollback é liberar cada entrada do mapa stale do mapa. A liberação de entradas stale é necessária para que qualquer transação subsequente que acessa a mesma chave ou chaves resulte no método get da interface do Loader que está sendo chamada para atualizar as entradas do mapa com os dados atuais do banco de dados.