Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Escreva aplicativos de acesso a dados Java de alto desempenho, Parte 3: Melhores práticas de API pureQuery

Aprenda sobre as melhores práticas do pureQuery examinando fragmentos de código e cenários do mundo real.

Vitor Rodrigues, Software Engineer, IBM
photo2
Vitor Rodrigues é desenvolvedor de software na equipe IBM Data Studio Developer e trabalha no laboratório Silicon Valley. Formou-se na Universidade de Minho, Portugal, em ciência da computação e em engenharia de sistemas. Vitor passou a fazer parte da IBM em 2005 como estagiário, trabalhando em DB2 Everyplace e em DB2 9 pureXML. Antes de entrar na equipe de desenvolvedores Data Studio, ele foi membro da equipe Technical Enablement para DB2 pureXML e IBM Data Studio, trabalhando fora dos laboratórios IBM Toronto e Silicon Valley.

Resumo:  pureQuery é uma plataforma de acesso a dados de alto desempenho que facilita o desenvolvimento, a otimização, a segurança e o gerenciamento de acesso a dados. É composta por ferramentas, APIs, um tempo de execução e serviços de monitoramento do cliente. Os artigos anteriores desta série apresentaram o uso dos objetos de acesso a dados (DAOs) e os métodos sequenciais integrados para acessar o banco de dados. Este artigo resume algumas melhores práticas de desenvolvimento usando pureQuery e fornece a você cenários reais que mostram como implementar essas práticas. [23 de setembro de 2010: Este artigo foi atualizado para incluir alterações de nome de produto e recursos adicionais disponibilizados desde a sua publicação original em agosto de 2008. --Ed.]

Visualizar mais conteúdo nesta série

Data:  17/Mai/2011
Nível:  Avançado Também disponível em :   Inglês
Atividade:  1491 visualizações
Comentários:  


Introdução

Leia artigos importantes sobre pureQuery

Os primeiros dois artigos desta série descreveram em detalhes como usar métodos sequenciais e como usar interfaces d e objeto de acesso a dados (DAOs) para acessar o banco de dados. Este terceiro artigo fornece um insight das inúmeras melhores práticas de desenvolvimento com o uso de pureQuery. A maior parte dessas práticas explora os recursos avançados da API pureQuery. Sempre que aplicável um cenário real é usado para mostrar o uso do recurso descrito. Os fragmentos de código incluídos são apenas para fins ilustrativos, mas devem dar uma boa ideia de como usar a API.

Escolher sequencial ou DAOs

Nos artigos anteriores desta série, os autores apresentaram casos de uso para DAOs e uso de métodos sequenciais.

As duas abordagens têm suas vantagens, portanto, aqui estão algumas coisas a serem consideradas quando se tenta decidir qual usar:

Use DAOs se você:

  • Preferir ter seu SQL separado da lógica de negócios
  • Quiser um código de camada de acesso a dados simples gerado pelo Optim Development Studio
  • Gostar de usar arquivos XML para definir a sua camada de aceso a dados
  • Tiver consultas predefinidas

Use os métodos sequenciais pureQuery se você:

  • Gostar de ter suas instruções SQL sequenciais no código Java, exatamente como programação JDBC regular
  • Tiver consultas geradas dinamicamente

Ainda não consegue se decidir? Se ainda não tiver certeza de qual deve usar, eu recomendo usar DAOs devido à capacidade de isolar o acesso a dados da lógica de negócios, o que simplifica tarefas como refatoração, porque o código está em um único lugar, e reutilização do código, compartilhando suas interfaces de acesso a dados entre projetos.

Consulta sobre coleções

Além de consultar bancos de dados relacionais, é possível usar pureQuery para consultar coleções Java na memória usando a mesma linguagem de consulta — SQL. Isso cria uma integração perfeita entre o banco de dados e o mundo Java. Em um ambiente distribuído, os trips de rede para o banco de dados são geralmente uma das operações mais caras, portanto, é possível usar essa abordagem de consulta alternativa para evitar algumas das operações caras.

Quando você usa consulta sobre coleção, sua consulta pode ser executada com relação a conjuntos de resultados existentes sem a necessidade de reavaliar a consulta no banco de dados e buscar novamente todos os dados no seu aplicativo. É possível também usar esse recurso para executar operações de junção entre duas ou mais coleções Java.

Cenário: Exibir catálogo de produtos

Suponha que o seu aplicativo da Web precise exibir um catálogo de produtos filtrados por uma marca específica. Na mesma página da Web, há uma estrutura em destaque no lado direito, que mostra os produtos mais vendidos na mesma categoria.

Em um aplicativo típico, pelo menos duas chamadas de banco de dados seriam necessárias para gerar essa página: uma para buscar todos os produtos a serem exibidos no catálogo principal e outra para buscar os produtos mais vistos para seleção no lado direito da tela. Com a consulta sobre coleções do pureQuery, é possível melhorar o desempenho do aplicativo reutilizando o primeiro conjunto de resultados, que contém todos os produtos da categoria selecionada. pureQuery consegue executar uma instrução SQL nesse conjunto de resultados existente, filtrando apenas os produtos com o status de bestseller.

O exemplo na Listagem 1 mostra um exemplo de métodos sequenciais para realizar essa filtragem, mas as consultas sobre coleções estão disponíveis para DAOs e métodos sequenciais.

Para consultar uma coleção na memória Java, é preciso obter uma instância de uma interface de dados não associada a uma conexão de banco de dados. O fato de não haver nenhuma conexão associada informa a pureQuery que você está consultando dados na memória e não dados armazenados em um banco de dados.


Listagem 1. Consultar um conjunto de resultados existentes residentes na memória
				
public void displayProducts(String category){
	Data db = DataFactory.getData(getConnection());
	List<Product> catalog = db.queryList("SELECT PID, NAME, DETAILS, " 
		+ " PRICE, WEIGHT, CATEGORY, BRAND, SIZE, "
        + " DESCRIPTION, BESTSELLER FROM PDQ_SC.PRODUCT "  
        + " where CATEGORY = ?", Product.class, category);
	for (Product p : catalog){
		//list product on webpage
	}
	Data inMemData = DataFactory.getData();
	List<Product> bestsellers = inMemData.queryList("SELECT * FROM " 
		+ " ?1.com.pureQuery.Product as prod WHERE prod.bestseller = 'Y'", 
		Product.class, catalog);
	for (Product p : bestsellers){
		//list bestseller
	}	
}
			

A linha 7 da Listagem 1 avalia uma consulta SQL sobre a coleção Java catalog , que hospeda todos os produtos de uma marca específica. Observe que a API usada para consultar essa coleção Java é a mesma usada para consultar um banco de dados. Se você tiver lido a Parte 2 desta série, deverá estar familiarizado com o método API queryList. Observe também o nome de classe qualificado na instrução SQL. Como as consultas que usam métodos sequenciais são avaliadas apenas no tempo de execução, será preciso especificar o nome completo de classe para obter assistência de conteúdo do conjunto de ferramentas do pureQuery quando você estiver digitando a consulta SQL e para que a API saiba quais tipos de objetos estão sendo usados.

Cenário: Gerar relatório de remessa

Considere o departamento de armazém de uma empresa de e-retailing (varejo ou comércio eletrônico). Após uma ordem de pagamento ter sido liberada, uma solicitação será enviada ao armazém para enviar o conteúdo desse pedido. O armazém tem softwares de gerenciamento que recebem o ID da ordem e usa essas informações para consultar a tabela ORDER_ITEMS para verificar quais produtos compõem a ordem e devem ser enviados ao cliente. Após tomar conhecimento de todos os produtos da ordem, o software gera uma lista com o nome do produto e o local (aisle e bin), de forma que os funcionários do armazém possam buscar o produto em seus locais e adicioná-los ao pacote de compra. Como as informações do local do produto são frequentemente usadas, elas são mantidas na memória pelo aplicativo, na forma de objetos locator . O seguinte fragmento de código mostra como unir as informações de produtos e localizador de uma ordem para gerar o relatório de remessa a ser usado pelos funcionários do armazém:


Listagem 2. Unindo duas coleções na memória com o uso de pureQuery
				
public List<ProductInfo> generateShippingReport(String orderID){
	Data db = DataFactory.getData(getConnection());
	List<Locator> locators = LocatorUtil.getLocators();
	Iterator<Product> products = db.queryIterator("SELECT p.* from PRODUCT AS p, " 
	+ " ORDER_ITEMS AS po where p.pid = po.pid and po.poid = ?", 
	Product.class, orderID);
	Data inMemData = DataFactory.getData();
	List<ProductInfo> shippingReport = inMemData.queryList("SELECT pr.pid, "  
		+ " pr.name, lr.aisle, lr.bin FROM ?1.com.pureQuery.Product AS pr, " 
		+ " ?2.com.pureQuery.Locator AS lr where pr.pid = lr.pid",   
		ProductInfo.class, products, locators);
	return shippingReport;
}

Observando em mais detalhes a Listagem 2, observe que as informações de local são geradas pela lógica de negócios, enquanto as informações do produto são buscadas no banco de dados. Como na Listagem 1, é preciso criar uma instância da interface de dados não associada a uma conexão de banco de dados para executar consultas sobre objetos na memória.

Após executar a instrução de junção, shippingReport terá informações mescladas das coleções locators e products .

Mecanismo de retorno conectável com o uso da interface Hook

A interface de dados de pureQuery permite anexar ganchos de instrução às conexões deles. Um gancho é similar em funcionalidade a um acionador do banco de dados. Ela apresenta uma forma de definir a funcionalidade executada antes e/ou depois da execução de cada chamada API. É possível aproveitar as vantagens desse recurso de várias formas:

  • Monitoramento de desempenho: é possível usar ganchos para medir aspectos de tempo de execução de chamadas API, como tempo de execução, rede e E/S.
  • Validar dados: ganchos de instrução permitem validar dados de parâmetro antes da execução da instrução, oferecendo a oportunidade de realizar verificação de restrição e validação de dados no nível de aplicativo.
  • SQL de auditoria: se precisar fazer a auditoria de todas as instruções SQL executadas pelo seu aplicativo pureQuery, os ganchos fornecerão uma forma fácil de fazer isso.

O fato de os ganchos serem simplesmente anexados ao objeto de dados torna o seu aplicativo ignorado para ele (com exceção do fragmento de código em que você cria seu objeto de dados). Por esse motivo, é possível conseguir toda a funcionalidade já descrita sem a necessidade de refatorar qualquer um de seus códigos.

Cenário: Implementando um monitor de desempenho

Vamos ver como usar um gancho para implementar uma solução de monitoramento de desempenho para o seu aplicativo.

A primeira etapa é definir a classe que implementa a interface Hook pureQuery Hook. A Listagem 3 mostra o código usado para fazer isso.


Listagem 3. SystemMonitorHook, usado para monitorar o desempenho de acesso a banco de dados
				
public class SystemMonitorHook implements Hook
{
  DB2SystemMonitor systemMonitor;

  public void pre (String methodName, Data dataInstance, 
  	SqlStatementType sqlStatementType, Object... parameters)
  {
    try {
     systemMonitor = ((DB2Connection)dataInstance.getConnection()).getDB2SystemMonitor();
     systemMonitor.enable (true);
     systemMonitor.start (DB2SystemMonitor.ACCUMULATE_TIMES);
    }
    catch (SQLException e) {
      throw new RuntimeException ("Unable to start system monitor " + e.getMessage ());
    }
  }

  public void post (String methodName, Data dataInstance, Object returnValue, 
  	SqlStatementType sqlStatementType, Object... parameters)
  {
    try {
      systemMonitor.stop ();
      System.out.println("Performance of method: " + methodName  + ":");
      System.out.println ("Application Time " + 
      	systemMonitor.getApplicationTimeMillis () + "milliseconds");
      System.out.println ("Core Driver Time " + 
      	systemMonitor.getCoreDriverTimeMicros () + "microseconds");
      System.out.println ("Network Time " + 
      	systemMonitor.getNetworkIOTimeMicros () + "microseconds");
      System.out.println ("server Time " + 
      	systemMonitor.getServerTimeMicros() + "microseconds");      
    }
    catch (SQLException e) {
      throw new RuntimeException
      		("Unable to stop system monitor " + e.getMessage ());
    }
  }

}

A classe SystemMonitorHook implementa a interface Hook de pureQuery, que declara os métodos pre() e post(). Esses dois métodos são executados antes e depois da execução de uma chamada API pureQuery. Listagem 3 usa parte da API proprietária do IBM Data Server Driver para JDBC e SQLJ (geralmente conhecidos como "driver JCC"). Para saber mais sobre essa API, consulte a página DB2 Information Center sobre o IBM Data Server para JDBC.

Esse gancho pode ser usado por qualquer um dos seus aplicativos pureQuery. Para acionar seu uso, é preciso apenas anexá-lo à instância de dados, como mostra a Listagem 4:


Listagem 4. Associando um gancho a uma conexão
				
//...
Connection con = getConnection();
SystemMonitorHook monitorHook = new SystemMonitorHook();
Data data = DataFactory.getData(CustomerData.class, con, monitorHook); 
// ...

data.queryList("select * from pdq_sc.product", Product.class);

// ...

Ao anexar o gancho à instância de dados, você ativa o mecanismo de monitoramento, que é executado para cada chamada de API.

A saída da chamada queryList de exemplo na Listagem 4 é mostrada na Figura 1:


Figura 1. Saída do gancho de System Monitor

Várias métricas de desempenho são impressas pelo monitor, incluindo os tempos de aplicativo, driver, rede e servidor.

queryList e queryArray versus queryIterator

pureQuery fornece três métodos de API que retornam coleções de objetos Java: queryArray, queryListe queryIterator. A regra básica é que você deve usar o método com melhor correspondência com o tipo de coleção que o aplicativo está esperando, para que conversões de tipo sejam evitadas.

Entretanto, há mais para esses métodos do que apenas tipos de retorno diferentes. A forma como esses métodos funcionam nos bastidores é também importante e deve ser levada em consideração no desenvolvimento do seu aplicativo. Enquanto queryArray e queryList fazem a materialização acelerada do conjunto de resultados, queryIterator faz a materialização lenta, buscando dados on demand à medida que o método Iterator.next() é chamado.

Considere as seguintes dicas ao decidir quais métodos API usar.

Use queryArray ou queryList quando:

  • Quiser percorrer o conjunto de resultados várias vezes.
  • Seu aplicativo puder alocar memória suficiente para carregar todos os dados na coleção.

Use queryIterator se:

  • Quiser fazer uma paginação dos resultados. Os dados são buscados on demand à medida que você exibe novas páginas.
  • Seu aplicativo tem uma quantidade reduzida de memória disponível.

Aproveitar as vantagens do envio em lote pureQuery

As próximas duas seções descrevem como aproveitar as vantagens dos recursos de envio em lote fornecidos por pureQuery para se obter os tipos homogêneos e heterogêneos de envio em lote.

Atualizações de lote homogêneas

Um requisito comum nos aplicativos de banco de dados é inserir várias linhas na mesma tabela como parte da mesma operação. JDBC fornece recursos de envio em lote; entretanto, esses recursos são muito detalhados (é preciso definir os parâmetros de instrução manualmente) e de certa forma complexos para usar.

Para ajudar na requisição de atualizações de batch associadas a uma API de fácil utilização, pureQuery sequencial fornece o método updateMany. Esse método pode ser usado para envio em lote homogêneo e recebe somente dois parâmetros: a instrução SQL de atualização e uma coleção de objetos Java a ser enviada em lote para a chamada de atualização. Em segundo plano, updateMany implementa a melhor prática JDBC de atualizações de lote, reduzindo drasticamente o número de trips de rede necessários para atualizar os dados. Ele também assegura que todas as atualizações ocorram em uma única transação.

Cenário: Atualizar banco de dados de produto

Toda semana, um aplicativo backend recebe várias atualizações de parceiros com a lista de novos produtos que eles têm disponível para venda. Seu aplicativo precisa atualizar o banco de dados usado pelo aplicativo de loja on-line, de modo que esses novos produtos sejam exibidos quando um usuário navegar pelo catálogo. A forma mais rápida e fácil de fazer isso é usando o método de API updateMany() mostrado na Listagem 5:


Listagem 5. Chamada API de atualização de lote homogêneo
				
//...
Data db = DataFactory.getData(getConnection());
List<Product> prods = getNewProducts();
db.updateMany("INSERT INTO PRODUCT (PRODUCTID, NAME, DETAILS, LISTPRICE,"
		+ " WEIGHT, CATEGORY, BRAND, SIZE, DESCRIPTION)"
		+ " VALUES (:productid, :name, :details, :listprice, :weight, :category,"
		+ " :brand, :size, :description)", prods);


Observe que somente uma chamada de API única é necessária para atualizar várias linhas na tabela de banco de dados. pureQuery armazena em lote a inserção dos novos produtos contidos na variável prods.

Ao usar DAOs pureQuery (implementados com o uso dos métodos anotados),o lote homogêneo ficará implícito. Se um método tiver uma coleção de beans como parâmetro, pureQuery interpretará isso como uma chamada em lote homogênea.

Atualizações em lote homogêneas

Embora o método updateMany de pureQuery forneça uma forma otimizada de fazer envio em lote de parâmetro com uma única instrução SQL para uma única tabela, às vezes os aplicativos precisam de operações mais complexas do que essas, como atualizar mais de uma tabela.

Cenário: Atualização de ordens de compra

Considere um cenário em que uma ordem de compra do cliente é armazenada em duas tabelas: ORDER com o resumo da ordem e ORDER_ITEMS com a lista de itens de cada ordem. O padrão SQL não fornece uma forma de inserir/atualizar/excluir várias tabelas em uma única instrução, portanto, os aplicativos precisam executar duas instruções separadas, uma para cada tabela. Mesmo se você usar lote homogêneo para atualizar a tabela ORDER_ITEMS, seu aplicativo ainda precisará executar duas chamadas de rede separadas — uma para atualizar a tabela ORDER e outra para atualizar a tabela ORDER_ITEMS. Além disso, você mesmo precisará controlar a transação para ter certeza de que o banco de dados está em um estado consistente após a atualização das duas tabelas.

A Figura 2 mostra o relacionamento um-para-muitos entre as tabelas ORDER e ORDER_ITEMS usadas para esse cenário.


Figura 2. Relacionamento um-para-muitos entre ORDER e ORDER_ITEMS

pureQuery inclui suporte para atualizações de lote heterogêneas. Uma atualização de lote heterogênea permite combinar várias instruções SQL com ou sem marcadores de parâmetro em uma única chamada de rede. Nesse cenário, é possível usar uma atualização heterogênea para atualizar as tabelas ORDER e ORDER_ITEMS em uma única operação de banco de dados. Enquanto o envio em lote JDBC suporta apenas o envio em lote de instruções com literais, o lote heterogêneo pureQuery suporta o envio em lote de instruções parametrizadas. Ao suportar instruções parametrizadas, você explora os recursos avançados fornecidos por eles, como reutilização de caminho de acesso e prevenção à injeção de SQL.

Embora as operações em lote homogêneas sejam obtidas dentro de uma chamada de API única que opera apenas em uma tabela, as operações em lote heterogêneas podem incluir várias chamadas de API e até mesmo amplitude em vários objetos de acesso a dados, o que afeta várias tabelas. Isso se torna muito útil quando é necessário ter uma combinação de métodos sequenciais e anotados e/ou chamadas para interfaces de método diferentes dentro da mesma transação.

A Listagem 6 usa o lote heterogêneo de pureQuery para atualizar várias tabelas, executando várias chamadas para a interface do método definido pelo usuário OrderData :


Listagem 6. Atualização de lote heterogênea usando a API de pureQuery
				
public void inserPurchaseOrder(PurchaseOrder po, String poid){
	OrderData orderData = DataFactory.getData(OrderData.class, getConnection());
	//start batch
	((Data)orderData).startBatch(HeterogeneousBatchKind.heterogeneousModify__);
	//create new order
	orderData.insertNewPurchaseOrder(po);
	//add items to order
	for (OrderItem oi : po.getItems())
	{
		orderData.addItemToPurchaseOrder(poid, oi);
	}
	// end batch
	((Data)orderData).endBatch();
}

Observe que os métodos startBatch() e endBatch() pertencem à interface com.ibm.pdq.runtime.Data, portanto, é preciso executar o cast do seu objeto OrderData para a interface de dados antes de chamar esses métodos. Como segunda opção, é possível fazer com que a sua interface OrderData estenda a interface de dados, para que a execução do cast não seja necessária. Entre startBatch() e endBatch(), você tem todas as chamadas de interface do método anotadas que deseja executar em uma única transação de lote. Na Listagem 6, suas várias chamadas API dentro do bloco de lote assegura que todas as informações relacionadas a uma ordem de compra serão atualizadas em uma única transação.

Mas há mais coisa para o lote heterogêneo de pureQuery! Além do suporte à execução de várias operações no mesmo objeto OrderData , o lote de pureQuery também suporta a agregação de operações dos métodos anotados (DAOs) e dos métodos sequenciais, o que significa que é possível combinar ambos dentro da mesma operação em lote. Suponha que você queira reutilizar o exemplo acima e adicionar outra operação que atualize o inventário de produtos, diminuindo a quantidade de produtos para cada item na ordem de compra. Além do mais, vamos dizer que você queira executar essa operação usando métodos sequenciais, mas deseja inserir a ordem de compra no banco de dados usando métodos anotados. A Listagem 7 mostra código similar ao que você utilizaria para implementar essas alterações.


Listagem 7. Atualização de lote heterogênea usando objetos de dados diferentes
				
public void inserPurchaseOrder(PurchaseOrder po, String poid){
	Data data = DataFactory.getData(getConnection());
	OrderData orderData 	= DataFactory.getData(OrderData.class, data);
	//start batch
	data.startBatch(HeterogeneousBatchKind.heterogeneousModify__);
	//create new order
	orderData.insertNewPurchaseOrder(po);
	//add items to order
	for (OrderItem oi : po.getItems())
	{
		orderData.addItemToPurchaseOrder(poid, oi);
	}
	//update inventory
	data.updateMany("UPDATE INVENTORY SET " +
			" QUANTITY = QUANTITY - 1 WHERE PRODUCTID = :pid", 
			po.getItems());
	// end batch
	data.endBatch();
}

Como mostrado na Listagem 7, os dois métodos dos objetos data e orderData estão sendo chamados dentro da execução em lote. Embora o código esteja se referindo a dois objetos diferentes, essas chamadas são executadas dentro de uma única operação de lote, porque os dois objetos fazem referência ao mesmo de dados subjacente (observe o segundo parâmetro da chamada para DataFactory.getData()).


Listagem 8. Interface OrderData
				
public interface OrderData {
	  //insert a new purchaseOrder
	@Update(sql = "insert into DB2ADMIN.ORDER(orderid, customerid, numberofitems, " +
	" numberofproducts, subtotaloforder, taxamount, totalamount, timestamp) " +
	" values(:orderid, :customerid, :numberofitems, :numberofproducts, " + 
	" :subtotaloforder, :taxamount, :totalamount, :timestamp)")
	void insertNewPurchaseOrder(PurchaseOrder po);
	  
	//add product to PurchaseOrder
	@Update(sql="insert into DB2ADMIN.ORDER_ITEMS(orderid, productid, quantity, cost)"
		+ " values(?1, ?2.pid, ?2.quantity, ?2.price)")
	void addItemToPurchaseOrder(String poID, OrderItem p )
}

A Listagem 8 mostra os métodos da interface OrderData, detalhando as chamadas insertNewPurchaseOrder e addItemToPurchaseOrder. Observe que, no método addItemToPurchaseOrder , parâmetros nomeados são usados para especificar qual variável do objeto po deve ser usada como o valor de parâmetro. No método addItemToPurchaseOrder , uma combinação de parâmetros numerados e nomeados é usada para se referir aos valores de parâmetro.

Da mesma forma que os métodos sequenciais e DAOs são usados no exemplo de lote heterogêneo, é possível também usar várias interfaces de método anotadas (DAOs) na mesma operação em lote heterogênea. Para isso, todos os DAOs precisam ser criados com o uso do mesmo objeto de dados base.

Customize seus conjuntos de resultados usando ResultHandlers e RowHandlers

pureQuery fornece algumas funções de mapeamento de objeto-tabela básicas que podem ser muito úteis quando você está desenvolvendo sua camada de acesso a dados. O Optim Development Studio ajuda a automatizar essa etapa fornecendo um conjunto de ferramentas para gerar Java beans que mapeiam para tabelas de banco de dados, que podem aumentar sua produtividade.

Entretanto, os aplicativos às vezes precisam de mapeamento mais complexo que pode ser conseguido com um mapeamento de objeto-tabela simples. Há ocasiões em que é preciso mapear apenas um subconjunto de uma tabela para um objeto Java; embora, em outros casos, seja possível mapear uma linha de tabela para vários objetos.

Além disso, é um requisito comum para transformar conjuntos de resultados em um formato não relacional, como HTML, XML ou objetos Java customizados.

pureQuery permite que os usuários implementem padrões de mapeamento customizados que podem ser usados para atender às necessidades descritas acima.

Cenário: exibir vários conjuntos de resultados em HTML

Considere um aplicativo que precisa exibir vários conjuntos de resultados no formato HTML. Uma forma de automatizar essa tarefa é usar os manipuladores de resultados de pureQuery. Um manipulador de resultados é usado para converter o conteúdo de um conjunto de resultados em um objeto. No próximo exemplo, o manipulador de resultados processa um conjunto de resultados e retorna uma página HTML, exibindo o conteúdo do conjunto de resultados como uma tabela.

Aqui está um fragmento da minha classe HTMLHandler:


Listagem 9. Manipulador do conjunto de resultados que gera páginas HTML
				
public class HTMLHandler implements ResultHandler<String>
{

  private DocumentBuilderFactory documentBuilderFactory_;
  private DocumentBuilder domBuilder_;
  private Transformer transformer_;

  public HTMLHandler ()
  {
	// ... initialize variables
  }

//...

  public String handle (ResultSet resultSet)
  {
    Document document = domBuilder_.newDocument ();

    // Create root element
    Element rootElement = document.createElement ("html");
    rootElement.setAttribute ("xmlns", "http://www.w3.org/TR/REC-html40");
    document.appendChild (rootElement);
    Element headElement = document.createElement ("head");
    rootElement.appendChild (headElement);
    Element titleElement = document.createElement ("title");
    titleElement.setTextContent ("HTML Table Printer");
    rootElement.appendChild (titleElement);
    Element bodyElement = document.createElement ("body");
    rootElement.appendChild (bodyElement);
    generatedTable (resultSet, bodyElement, document);
    return transformXML (document);
  }

  private void generatedTable (ResultSet resultSet, Element bodyElement, 
  		Document document)
  {
      ResultSetMetaData resultSetMetaData = resultSet.getMetaData ();
      int columnCount = resultSetMetaData.getColumnCount ();
      Element tableElement = document.createElement ("TABLE");
      tableElement.setAttribute ("border", "1");
      bodyElement.appendChild (tableElement);
      Element headerRowElement = document.createElement ("TR");
      tableElement.appendChild (headerRowElement);

      for (int index = 0; index < columnCount; index++) {
        Element headerElement = document.createElement ("TH");
        headerElement.setTextContent (resultSetMetaData.getColumnLabel (index + 1));
        headerRowElement.appendChild (headerElement);
      }
      while (resultSet.next ()) {
        Element rowElement = document.createElement ("TR");
        tableElement.appendChild (rowElement);
        for (int index = 0; index < columnCount; index++) {
          Element columnElement = document.createElement ("TD");
          columnElement.setTextContent (resultSet.getString (index + 1));
          tableElement.appendChild (columnElement);
        }
      }
  }

// ... 
}

Para fins de simplicidade, a Listagem 9 mostra apenas uma parte do arquivo HTMLHandler.java.

Para usar este e outros manipuladores de resultados, a API pureQuery fornece o método query() . Esse método recebe parâmetros, incluindo uma instrução SQL e um manipulador de resultados, e retorna um objeto do tipo genérico T. Esse tipo é definido pelo tipo de tempo de execução T da interface RowHandler<T> parametrizada. No exemplo na Listagem 10, o HTMLHandler processa um conjunto de resultados e retorna um objeto do tipo String, que contém a representação textual de uma página da Web que lista todas as linhas do conjunto de resultados.

Para converter o resultado da consulta em uma página HTML, simplesmente passe um HTMLHandler à chamada de API:


Listagem 10. Passando o manipulador de resultados HTMLHandler à chamada de API
				
public String generateProductList(){
	Data db = DataFactory.getData(getConnection());
	String htmlpage =  db.query("SELECT * from PRODUCT", new HTMLHandler());
	return htmlpage;
}

Cenário: Manipulação de endereços de estruturas diferentes

Geralmente, uma linha em uma tabela de banco de dados pode armazenar informações de diferentes objetos no seu aplicativo Java. Considere a tabela ADDRESS, que contém endereços dos meus clientes. Embora o exemplo use somente uma tabela para armazenar essas informações, vários países têm estruturas de endereços diferentes. Isso geralmente faz com que colunas de tabela não usadas ou o uso da mesma coluna armazenem propriedades diferentes. Por exemplo, estados dos EUA e províncias do Canadá podem ser salvos em uma coluna com o nome “STATE”, embora, em seus Java beans, seja necessário ter claramente variáveis com o nome state e province.

Vamos definir sua interface de endereço . Seu aplicativo recupera endereços dos clientes a partir do banco de dados e os imprime no formato típico usado em rótulos de endereço, de modo que possam ser afixados às caixas de remessa. O único método que precisa ser implementado é printableFormat() , que retorna o endereço como ele deve ser impresso.


Listagem 11. Interface de endereço de amostra
				
public interface Address {

	public String printableFormat();
	
}	

Como você tem clientes nos EUA e no Canadá, você tem duas implementações da interface de endereço:


Listagem 12. Classes Java USAddress e CANAddress
				
public class USAddress implements Address {

	protected String customerName;
	protected String street;
	protected String city;
	protected String state;
	protected String zipcode;	

	//...
}

public class CANAddress implements Address {
	protected String customerName;
	protected String street;
	protected String city;
	protected String province;
	protected String postalCode;

	//...
}	

No tempo de execução, seu RowHandler decide qual é o objeto apropriado a ser retornado para a linha do conjunto de resultados atual:


Listagem 13. Manipulador de resultados de endereço
				
public class AddressHandler implements RowHandler<Address>  {
	public Address handle(ResultSet rs, Address object) throws SQLException {
		Address addr = null;
		if (rs.getString(3).equals("United States")){
			USAddress us = new USAddress();
			us.setCustomerName(rs.getString(2));
			us.setStreet(rs.getString(4));
			us.setCity(rs.getString(5));
			us.setState(rs.getString(6));
			us.setZipcode(rs.getString(7));
			addr = us;
		} else if (rs.getString(3).equals("Canada")){
			CANAddress can = new CANAddress();
			can.setCustomerName(rs.getString(2));
			can.setStreet(rs.getString(4));
			can.setCity(rs.getString(5));
			can.setProvince(rs.getString(6));
			can.setPostalCode(rs.getString(7));
			addr = can;
		}
		return addr;
	}
}

No seu aplicativo, refira-se a todos os objetos como objetos do tipo Address, em vez de precisar lidar com os dois tipos de endereços:


Listagem 14. Processando o conjunto de resultados usando AddressHandler
				
//...
Data db = DataFactory.getData(getConnection());
List<Address> addrs = db.queryList("SELECT * FROM CUSTOMERADDRESS", 
				new AddressHandler());
// process list of Address objects…	

Para fins de simplicidade, esse exemplo não mostra chamadas a nenhum método de interface. Em um aplicativo em situação real, a interface de endereço define vários métodos para trabalhar com endereços, independentemente de ser um objeto USAddress ou CANAddress.

Definir a granularidade mais apropriada para objetos de acesso a dados

No desenvolvimento de aplicativos usando DAOs, é importante definir a granularidade mais apropriada. Embora não exista nenhuma receita mágica para o modo como a granularidade deve ser definida, há certamente algumas diretrizes que podem ajudar a alcançar o design que atenda melhor às suas necessidades:

  • Caso a sua camada de acesso a dados seja muito específica de aplicativo, ou seja, tenha código de acesso a dados usado apenas pelo aplicativo que você está desenvolvendo no momento, será uma boa ideia agregar todos os códigos de acesso a dados na mesma interface. Usando essa abordagem, cada um dos seus aplicativos tem sua própria interface, tornando o seu gerenciamento mais fácil. (A Figura 3 apresenta uma visualização arquitetural.)

Figura 3. Arquitetura específica de aplicativo

  • Por outro lado, se você estiver desenvolvendo código de acesso a dados que será solicitado por vários aplicativos, será preciso separar esse código em unidades lógicas e criar uma interface DAO para cada unidade lógica (consulte a Figura 4). Com essa abordagem, seus aplicativos conseguem compartilhar interfaces de acesso a dados, reutilizando o código e reduzindo a quantidade de trabalho necessária para desenvolver o aplicativo completo.

Figura 4. Arquitetura de unidade lógica

Para algumas pessoas, a criação de um DAO para cada tabela de banco de dados pode parecer ser uma abordagem muito direta. Entretanto, a separação do acesso a dados em unidades lógicas em vez de unidades de dados pode ser considerada uma abordagem melhor pelos seguintes motivos:

  • Raramente um aplicativo acessa somente uma tabela, portanto, com a adoção dessa abordagem, algumas operações precisarão ser instanciadas e trabalhar com mais de uma interface. Por exemplo, salvar uma ordem no banco de dados requer a atualização das tabelas ORDER e ORDER_ITEM, dessa forma, uma interface OrderData que funciona com as tabelas ORDER e ORDER_ITEMS é mais apropriada do que ter duas interfaces separadas. O mesmo é verdadeiro para recuperar dados; toda vez que você recuperar dados da tabela ORDER_ITEMS, será preciso também obter as informações da ordem, para que a tabela ORDER seja acessada também. As unidades lógicas devem ser criadas para agregar o acesso a banco de dados a objetos relacionados.
  • Se for usar lote heterogêneo, será preciso coletar as instruções SQL que você quer armazenar em uma interface para tornar mais simples o uso de lote heterogêneo.

Implementar paginação usando um manipulador de paginação

Paginação é uma abordagem comum usada para exibir grandes quantidades de dados. Aplicações como catálogos na Web, relatórios de vendas de várias páginas etc. fazem uso intenso de paginação, buscando um novo bloco de dados para cada nova página exibida.

pureQuery fornece um manipulador de resultados chamado IteratorPagingResultHandler. Assim como qualquer outro ResultHandler, passe esse manipulador aos métodos do objeto Data ao chamar a API. O construtor desse manipulador permite que você especifique várias opções, incluindo a classe dos beans sendo retornados do conjunto de resultados ou até mesmo um RowHandler para manipular cada uma das linhas retornadas. Com base na especificação de como os dados são retornados, é possível também especificar quais dados (e em qual quantidade) devem ser retornados do banco de dados, de acordo com dois esquemas diferentes:

  • Recuperação de bloco: todas as linhas no intervalo especificado pelos parâmetros absoluteStartingRow e absoluteEndingRow são recuperadas.
  • Recuperação de página: o aplicativo especifica os parâmetros pageSize e pageNumber. O manipulador retorna a página pageNumbera dos dados contendo as linhas pageSize.

A Listagem 15 é um exemplo de como usar IteratorPagingResultHandler:


Listagem 15. Resultados da página usando IteratorPagingResultHandler
				
//...
int pageNumber = this.getCurrentPage();
int pageSize = this.getPageSize();
		
Iterator<Product> prods =  db.query("SELECT * from PRODUCT", 
	new IteratorPagingResultHandler<Product>(pageNumber,pageSize,Product.class));

// display Product objects…	

Na amostra de código acima, se o valor de pageNumber for 2, e o de pageSize, 15, a variável prods terá os produtos das linhas 16 a 30 na tabela PRODUCT.

Procedimento armazenado CallHandler

Os procedimentos armazenados têm sua própria função quando o assunto é objetos de banco de dados tendo vários valores de retorno. Embora as instruções SQL retornem conjuntos de resultados e valores de retorno UDFs (escalares e tabulares), os procedimentos armazenados podem retornar vários valores nos parâmetros OUT e INOUT, além dos vários conjuntos de resultados. Essa característica torna os procedimentos armazenados um recurso de banco de dados complexo de manipular usando JDBC. Para acessar todas as informações retornadas por uma chamada de procedimento armazenada, os desenvolvedores precisam registrar todos os parâmetros de saída antecipadamente e atribuir o resultado da chamada a um objeto ResultSet. Felizmente, para nós desenvolvedores, pureQuery fornece um tipo de objeto StoredProcedureResult que pode ser usado para armazenar todas as informações de saída geradas por uma chamada de procedimento armazenada incluindo parâmetros de saída e conjuntos de resultados.

A próxima amostra de código descreve como usar o tipo de objeto StoredProcedureResult para acessar todas as informações de saída de uma chamada de procedimento armazenada:


Listagem 16. Saída de procedimento armazenado usando StoredProcedureResult
				
//...
int medianSalary = 75000;

StoredProcedureResult spr = db.call ("call TWO_RESULT_SETS (?)", medianSalary);

String[] outParms = (String[])spr.getOutputParms ();

System.out.println ("Output Parameter(s) length: " + outParms.length);
System.out.println ("List of Products");

Iterator<Product> prods = spr.getIterator(Product.class);
while (prods.hasNext()) {
Product p = prods.next();
System.out.println("Name: " + p.getName());
}

spr.close ();  
// ...

Usando o objeto StoredProcedureResult , evita-se a necessidade de registrar parâmetros de saída antes de chamar o procedimento armazenado. Isso não só reduz a quantidade de código a ser digitada, mas também simplifica o processo, porque você está lidando com apenas um único objeto, em vez de lidar com um conjunto de vários parâmetros de saída e o conjunto de resultados retornado pelo procedimento armazenado.

Processamento do cursor

Um dos aspectos que torna pureQuery uma abordagem exclusiva para persistência de dados é que, embora forneça várias etapas e mapeamentos automatizados, você nunca é privado do mesmo nível de controle obtido com mais abordagens de nível inferior, como programação JDBC simples. Você sempre tem a opção de aproveitar as vantagens dos mapeamentos automáticos e das APIs fornecidas de pureQuery ou assumir novamente o controle e implementar seus próprios manipuladores de conjuntos de resultados ou ganchos de conexão.

Se você precisar controlar como os dados são buscados no banco de dados, pureQuery fornecerá a opção de definir o tipo, o nível de simultaneidade e a capacidade de suspensão do cursor de banco de dados usado para buscar os dados. Essas configurações podem ser passadas como parâmetros aos métodos de API ou definidas como anotações pureQuery na utilização do estilo de método anotado. Os valores válidos para esses parâmetros refletem os valores suportados pela API JDBC (é possível verificar os valores suportados consultando a página JDBC ResultSet API).

Listagem 17 mostra uma chamada de API pureQuery usada para definir os parâmetros do cursor:


Listagem 17. Definindo atributos de cursor em uma chamada de API pureQuery
				
//...
Data db = DataFactory.getData(getConnection());
Iterator<Product> prods = db.queryIterator(java.sql.ResultSet.TYPE_FORWARD_ONLY, 
		java.sql.ResultSet.CONCUR_READ_ONLY, 
		java.sql.ResultSet.CLOSE_CURSORS_AT_COMMIT, 
		"SELECT * FROM PRODUCT", Product.class);
//...

A Listagem 17 usa o cursor para buscar todos os produtos no banco de dados para ser um cursor somente de encaminhamento executado no modo somente leitura e é fechado quando a transação é confirmada.

Recursos de fechamento

Embora pureQuery faça boa parte do gerenciamento dos recursos para você, melhorando o desempenho geral do seu aplicativo, há alguns casos em que é necessário estar preocupado com o fechamento de recursos.

pureQuery o ajuda ao automaticamente fechar instruções, conjuntos de resultados e cursores quando eles não estão mais em uso. Além disso, ele fecha objetos do tipo ResultIterator e StoredProcedureResult quando seu conteúdo é consumido. Caso a sua chamada de API retorne um desses tipos de objeto e você consuma todo o conteúdo, eles serão automaticamente fechados por pureQuery.

Se a lógica do aplicativo puder levar a situações em que o conteúdo dos objetos de resultado pode não ter sido totalmente buscado, você deverá fechar esses recursos explicitamente. Os objetos de dados deverão também ser fechados quando a parte de acesso a dados do seu aplicativo estiver concluída, de modo que o objeto de conexão associado a ele s seja liberado também.

A Listagem 18 mostra um exemplo de como fechar um objeto Iterator retornado por pureQuery:


Listagem 18. Fechando um objeto ResultIterator
				
//...
Iterator<Product> prods= db.queryIterator("SELECT * from DB2ADMIN.PRODUCT",Product.class);
		
// work with iterator variable "prods"
		
((ResultIterator<Product>)prods).close();

Como pureQuery retorna um objeto do tipo genérico Iterator, é preciso executar o cast da variável prods em um objeto do tipo ResultIterator<T>, visto que esse é o tipo de agente iterativo implementado por pureQuery e que fornece a funcionalidade close().

Resumo

Este artigo descreveu inúmeras boas práticas para os desenvolvedores de pureQuery. A lista incluiu recursos de API avançados, bem como regras básicas para decisões de desenvolvimento. Seguindo essas recomendações, espere um aumento da sua produtividade e escreva códigos mais limpos, visto que boa parte da carga de JDBC desaparece quando se utiliza a API de pureQuery.

Espero que este artigo seja um recurso útil para ajudá-lo no desenvolvimento de aplicativos pureQuery e no aproveitamento máximo de pureQuery. Aguardo o seu feedback sobre o artigo e sobre pureQuery.


Recursos

Aprender

Obter produtos e tecnologias

Discutir

Sobre o autor

photo2

Vitor Rodrigues é desenvolvedor de software na equipe IBM Data Studio Developer e trabalha no laboratório Silicon Valley. Formou-se na Universidade de Minho, Portugal, em ciência da computação e em engenharia de sistemas. Vitor passou a fazer parte da IBM em 2005 como estagiário, trabalhando em DB2 Everyplace e em DB2 9 pureXML. Antes de entrar na equipe de desenvolvedores Data Studio, ele foi membro da equipe Technical Enablement para DB2 pureXML e IBM Data Studio, trabalhando fora dos laboratórios IBM Toronto e Silicon Valley.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Information Management, Tecnologia Java
ArticleID=648347
ArticleTitle=Escreva aplicativos de acesso a dados Java de alto desempenho, Parte 3: Melhores práticas de API pureQuery
publish-date=05172011
author1-email=vrodrig@us.ibm.com
author1-email-cc=

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).