sexta-feira, 30 de agosto de 2013

SQLJ - Uma alternativa otimizada para acesso ao Oracle...



Você gostaria de escrever menos código nas suas aplicações java que lidam com chamadas sql a um banco de dados? você gostaria que suas declarações sql fossem verificadas durante a compilação e não em tempo de execução? então o sqlj pode ser o que você precisa! o sqlj possibilita o desenvolvimento rápido, com menos código, facilidade de depuração e otimização automática de desempenho.
este artigo assume que você possui conhecimentos básicos de jdbc e de seus métodos, assim como algum conhecimento de sql.
o que é sqlj?

O sqlj é essencialmente sql embutido, inserido em aplicações java. o sqlj foi desenvolvido para facilitar o desenvolvimento de projetos orientados a banco de dados, reduzindo significativamente o tempo de desenvolvimento/depuração.
problemas de codificação podem decorrer de erros de sintaxe e de outras questões semânticas que podem não estar corretas, ou simplesmente podem decorrer de nomes de coluna errados, causando a repetição do ciclo teste/depuração/codificação. a depuração é particularmente fácil com o sqlj porque você vê o fonte gerado e pode localizar depressa qualquer erro.
usando o sqlj, seu código fica mais manutenível e flexível. às vezes, as especificações da sua aplicação podem mudar e isto não nos deveria surpreender. você cria freqüentemente declarações sql complexas que combinam unions, joins, e múltiplas cláusulas com valores dinâmicos. porém, se você usar o sqlj, você verá que seu código permanecerá legível mesmo se contiver código sql extenso. por exemplo, com o sqlj você não precisa concatenar suas declarações sql; você pode escrever quantas declarações sql você quiser, contanto que sejam corretamente definidas. se você usar oracle, você pode usar qualquer package dbms, procedures, custom packages e todas as funções embutidas existentes na sua aplicação java. se você for um desenvolvedor pl/sql, você achará o sqlj muito útil para o desenvolvimento de software avançado com reutilização do seu estilo e do código pl/sql.
tradutor sqlj
A arquitetura do sqlj é composta por um conjunto de funcionalidades que atuando em conjunto permitem a tradução de comandos inseridos no sqlj para um determinado sql. neste contexto, o tradutor desempenha algumas funcionalidades principais:
·         verificação sintática dos construtores sql embutidos;
·         verificação de data type do java e sql;
·         verificação do schema.
Começando a trabalhar com o sqlj

1-    Java SDK instalado
2-    Oracle instalado
3-    Verificar se JAVA_HOME e ORACLE_HOME estão corretos.
4-    Jogar no path o arquivos SQLJ.exe
5-    Jogar no classpath as bibliotecas:
D:\oracle\ora81\sqlj\lib\translator.zip;
D:\oracle\ora81\sqlj\lib\runtime12ee.zip

Obs. Runtime.zip para JDK 1.1, runtime12.zip para JAVA2 e runtime12ee.zip para JAVA2EE. (estas bibliotecas se localizam no diretório [oracle]\sqlj\lib)
Obs2. Não existe pluggin para Eclipse ou Netbeans desenvolvimento com sqlj, no momento da geração do código Java e do class devera ser feita em linha de comando, daí sim compila-se o código Java na IDE.

Na listagem 1 temos uma sequence e uma tabela que serão como exemplo.

   create sequence customer_seq minvalue 1  maxvalue 999999999999999
      start with  1 increment by 1 cache 20;

   create table customer (id number(12) primary key , fullname varchar2(100)  null,
      street varchar2(100) null, city varchar2(100) null, province varchar2(100) null,
      country varchar2(100) null );

   commit;
listagem 1. sequence e tabela.

Escrevendo uma aplicação sqlj

Agora vamos ver um exemplo simples que utiliza o sqlj para recuperar dados das colunas fullname e street name (ver código da listagem 2).

import java.sql.*;    // este import é necessário para o sqlexception e outras classes jdbc
import oracle.sqlj.RUNTIME.oracle;

public class singlerowquery extends base {
   public static void main(string[] args) {
      try {
         connect();
         singlerowquery(1);
      } catch (sqlexception e) {
         e.printstacktrace();
      }
   }

   public static void singlerowquery(int id) throws SQLException{
      string fullname = null;
      string street = null;
      #sql { select fullname,  street into : out fullname,
            : out street from customer where id = :id};
      system.out.println("customer with id = " + id);
      system.out.println();
      system.out.println(fullname + " " + street);
   }
}
listagem 2. aplicação utilizando o sqlj.


Agora vamos analisar os trechos importantes da listagem 2.

Inicialmente temos o import oracle.sqlj.RUNTIME.oracle. este é parte do translator.jar e permite o uso de suas funcionalidades, a classe base lida com a questão da conexão.
vale ressaltar aqui que qualquer método que possua uma declaração sqlj sempre terá que possuir throws sqlexception. caso contrário, o tradutor não funcionará.


Agora, passemos para a parte de manipulação de dados. você talvez esteja um pouco confuso caso conheça o pl/sql: porque "into" está sendo usado neste contexto?
Neste exemplo, você está usando uma declaração select para salvar os valores em duas host variables especificando into : out.
·         : out denota os valores que são armazenados em variáveis do host;
·         : in denota as variáveis lidas. se você estiver usando um método para obter o valor, você tem que incluí-lo entre parênteses.

Variáveis Host

Variáveis host permitem que programas sqlj troquem informações entre o sql embutido e o restante da aplicação java. apesar deste nome diferenciado, é uma variável qualquer criada em seu programa java.
para exemplificar o uso dessas variáveis, vamos analisar o comando select da listagem 3. nele é ilustrado o uso da cláusula select into para recuperar as colunas first_name, last_name, dob e phone da tabela customers onde a coluna id seja igual a 2. os valores retornados são armazenados nas variáveis host. neste caso, as variáveis host declaradas na aplicação java foram id, first_name, last_name, dob e phone.

   // declarando variáveis host
   int id = 2;
   string first_name = null;
   string last_name = null;
   java.sql.date dob = null;
   string phone = null;

   // executa select
   #sql {
     select
       first_name, last_name, dob, phone
     into
       :first_name, :last_name, :dob, :phone
     from
       customers
     where
       id = :id
   };
listagem 3. entendendo variáveis host.


Agora que você finalizou o código, você pode aprofundar-se mais nas particularidades do sqlj. como você pode ver, a execução da declaração sqlj não é uma string regular e seu arquivo deve ter a extensão .sqlj. além disto, esta declaração tem a seguinte sintaxe:

   #sql {sql statement};



As características suportadas pelo sqlj são:

·         sql dml (data modification language): declarações como select, update, delete e insert;
·         declarações de controle de transações sql como commit e rollback;
·         sql ddl (data definition language) como create table e drop table;
·         chamadas a stored procedures, funções e pacotes oracle pl/sql;
·         diretivas de sessão (session directives).

Eis aqui um exemplo do procedimento de chamada ao pl/sql:

   #sql {
      // chamada à procedure
      call insertcustomerorder(1234,44);
   };

e outro exemplo de bloco executável que designa um valor pl/sql a uma variável host:

   #sql {
      declare
      loc_myid number := 1234;
      begin
         set :(:my_hostvariable) := loc_myid;
      end;
   };

código gerado pelo tradutor

Passemos do código escrito pelo desenvolvedor para o código gerado pelo tradutor. como você pode ver na listagem 4, o código gerado contém imports fundamentais para chamadas ao jdbc, conferência de resultados, execução nativa de uma consulta oracle e também gera todo o tratamento de exceções e algumas outras verificações. o código gerado também é otimizado quando o compilamos para bytecode através do javac.

// o código reflete o comando sql definido
...
//  ************************************************************
//  #sql { select fullname, street
//         from customer
//         where id = :id };
//  ************************************************************
{

  oracle.jdbc.oraclepreparedstatement __sjt_st = null;
  sqlj.RUNTIME.ref.defaultcontext
 _sjt_cc = sqlj.RUNTIME.ref.defaultcontext.getdefaultcontext();
if (__sjt_cc==null)
   sqlj.RUNTIME.error.RUNTIMEreferrors.raise_null_conn_ctx();
   sqlj.RUNTIME.executioncontext.oraclecontext __sjt_ec =
((__sjt_cc.getexecutioncontext()==null) ?
sqlj.RUNTIME.executioncontext.raisenullexecctx() :
__sjt_cc.getexecutioncontext().getoraclecontext());
   oracle.jdbc.oracleresultset __sjt_rs = null;
   try {
      __sjt_st =
         __sjt_ec.prepareoraclestatement(
            __sjt_cc,"0singlerowquery",
                     "select fullname,ntttt street
                      from customer where id =  :1");
      if (__sjt_ec.isnew())
      {
         __sjt_st.setfetchsize(2);
      }
      // designar os parâmetros in
      __sjt_st.setint(1,id);
      // execute query
      __sjt_rs = __sjt_ec.oracleexecutequery();
      if (__sjt_rs.getmetadata().getcolumncount() != 2)
sqlj.RUNTIME.error.RUNTIMEreferrors.raise_wrong_num_cols(
   2,__sjt_rs.getmetadata().getcolumncount());
      if (!__sjt_rs.next()) sqlj.RUNTIME.error.RUNTIMEreferrors.
         raise_no_row_select_into();
      // recuperação dos parâmetros out
      fullname = __sjt_rs.getstring(1);
      street = __sjt_rs.getstring(2);
      if (__sjt_rs.next()) sqlj.RUNTIME.error.RUNTIMEreferrors.
         raise_multi_row_select_into();
   } finally { if (__sjt_rs!=null) __sjt_rs.close();
      __sjt_ec.oracleclose(); }
}
...
// inserir aqui a impressão dos resultados
listagem 4. parte do código gerado pelo tradutor.


Conecção com Banco de dados
Para conecção a banco de dados utilizamos o context , seu formato básico é:

#sql context MyCtx;

E após isto temos duas opções, criarmos nossa conecção:

String url = "jdbc:vendor:myDB";
String user = "john";
String password = "secret";
MyCtx myCtx = new MyCtx(url, user, password, false);

Ou trabalharmos com uma conecção JDBC pré-existente:

MyCtx myCtx = new MyCtx(Connection conn);
Recuperação de linhas múltiplas

Em qualquer aplicação você pode precisar recuperar linhas múltiplas do banco de dados ou de algum outro datasource. se você usar a tecnologia sqlj, esta será uma tarefa fácil.
Em jdbc, você usaria um resultset para manipular um conjunto de registros. usando o sqlj, você usaria o que é chamado de iterator para atingir um resultado semelhante (se você conhece o pl/sql, você pode considerar o iterator como um cursor ref). os seguintes passos resumem as etapas que devem ser seguidas para se trabalhar com iterator:

·         use a declaração sqlj para definir a classe iterator;
·         declare uma instância do iterator;
·         povoe a instância do iterator com select;
·         use o método next() da classe iterator para recuperar a próxima linha;
·         extraia os valores de coluna do iterator atual usando os métodos da classe iterator;
·         desative ou dispense a instância do iterator invocando o método end().

Criação de um iterator em sqlj:

   #sql iterator << iterator name >>
      (<< list of attributes declarations) };

Instanciamento de um iterator:

   iterator_class_name  instance_name;
   // populando
   #sql  instance_name = {  select_statement };

na listagem 5 apresentamos um exemplo completo para recuperar uma lista de clientes.


// importações necessárias

#sql iterator customeritr implements scrollable
   ( int id, string fullname, string street);
#sql context Ctx;

// esta é a linha que será traduzida para a classe java chamada customeritr
// com os métodos id(), fullname(), street() alguns métodos principais, manipulador de conexão

public static void printcustomers(Connection conn) throws sqlexception {
      //declarando o objeto iterator que armazenará o resultado

      customeritr itr;    // instanciando o iterator
      //populando o iterator utilizando uma declaração select
     Ctx contx= new Ctx(conn);
    String nomecompleto= “manoel de sousa”;

     // :<variavel> insere variável Java no SQLJ
      #sql [contx]  itr = { select id, fullname,street from customer where fullname = :nomecompleto};
      system.out.println("idtnametstreettn");
         itr.afterlast();
      //lendo valores das colunas via iterator

      while(itr.previous()) {
         system.out.print(itr.id());
         system.out.print("t");
         system.out.print(itr.fullname());
         system.out.print("t");
         system.out.print(itr.street());
         system.out.println();
      }  

   itr.close();      //fechando o iterator
}
listagem 5. utilizando iterator.

perceba que os parâmetros do iterator devem ser iguais aos nomes das colunas referenciadas no banco de dados.
execução e compilação do sqlj
uma vez criado o arquivo sqlj, você vai querer compilá-lo e executá-lo. a sintaxe abaixo mostra como fazer isto:

> sqlj << filename.sqlj>> 

compilar o arquivo *.sqlj com as bibliotecas correspondentes do classpath resulta na geração dos arquivos *. java e *.class.


Acessando banco de dados sem CONTEXT

package br.com.abril.cev.integration.sqlj;
import java.sql.Connection;
import java.sql.SQLException;
import sqlj.runtime.ref.*;
import br.com.abril.cev.util.Converter;
import sqlj.runtime.ConnectionContext;

public class consultaPreCancelamento {

    private VarejoCtx vCtx;
    private Connection conn;
    private String qtde;

public consultaPreCancelamento(Connection conn, String nf, String cota) throws SQLException
{
            this.conn = conn;
            vCtx = new VarejoCtx(conn);
            try{
                   #sql [vCtx] {
                        select count(*) valor
                                   into :qtde
                        from varejo.item_extrato_devolucao ied,varejo.extrato_devolucao ed
                         where
                               ied.num_extrato_devolucao_ited = ed.num_extrato_devolucao_exdv and
                                    ied.num_nota_fiscal_ftf_ited = :nf  and ed.cod_cota = :cota
                    };
 }
finally{
    if(vCtx!=null)
            vCtx.close(false);
}
    }
   
    public String registros(){
            return this.qtde;
    }
}

Obs: Joga-se o valor de INTO diretamente para variáveis private, o método registros nos fornece o valor armazenado posteriormente.
conclusão

Escrever código enxuto e claro usando um back-end de banco de dados é parte essencial do desenvolvimento de software. qualquer tipo de arquitetura de software estará bastante amarrada a dados, tanto em aplicações web quanto em aplicações baseadas em formulários. se você está escrevendo um projeto pequeno e não tem um design de aplicação de três camadas, o sqlj será perfeito para você. ele lhe permite o desenvolvimento rápido de aplicações e também economiza tempo com correção de erros simples.