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.
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.
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
absoluteStartingRoweabsoluteEndingRowsã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.
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.
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().
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.
Aprender
- Visite a página da plataforma pureQuery para ter uma visão geral da plataforma pureQuery, incluindo o valor que ela proporciona, os produtos que a acompanham e FAQs.
- Veja como, em um grande projeto, foi tomada a decisão de mudar para pureQuery a partir de EJB2 nesta série: "Migrar de persistência gerenciada por contêiner EJB2 para pureQuery de IBM
Master Data Management Servery".
- O artigo"Novidades do Optim Development Studio 2.2" é atualizado com os aprimoramentos mais recentes desde a redação da série original acima, incluindo suporte a conjuntos de ferramentas para acesso a bancos de dados Oracle.
- Leia os outros artigos da série "Escreva aplicativos Java de alto desempenho".
- Assista ao vídeo Blackbox/Whitebox pureQuery Made Simple para ver uma pequena introdução, que compara pureQuery com outras abordagens de acesso a dados.
-
“No Excuses” Database Programming for Java (Kathy Zeidenstein e
Bill Bireley, maio de 2008, IBM Database Magazine) é um excelente artigo que explica os benefícios de SQL estático e como explorar esses benefícios usando Java.
- Leia a documentação de pureQuery para uma descrição completa do pureQuery Runtime.
-
Tutorial: siga passo a passo o tutorial no Integrated Data Management Information Center.
- Se estiver interessado em pureXML, leia o artigo em "Manipulação de dados pureXML em aplicativos Java usando pureQuery" para uma visão geral da plataforma pureQuery, incluindo o que ela é, com quais produtos vem e uma FAQ.
- Visite a área do página da plataforma pureQuery para ter uma visão geral da plataforma pureQuery, incluindo o valor que ela proporciona, os produtos que a acompanham e FAQs.
- Visite a página de recursos do developerWorks da família Optim para ler artigos e tutoriais e se conectar a outros recursos para expandir as suas qualificações de gerenciamento de dados integrado.
Obter produtos e tecnologias
-
pureQuery javadoc está disponível como parte da documentação on-line.
- Baixe e teste o IBM Optim Development Studio e pureQuery Runtime gratuitamente durante 30 dias.
Discutir
- Participar do fórum de discussão.
-
Integrated Data Management Community Space
- Participe do blog da equipe do Integrated Data Management.

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.