Introdução ao SQLJ

 

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?

Este artigo lhe dará toda informação necessária para começar a codificar em SQLJ preparando-o para a codificação da camada de dados de sua aplicação. 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 (ver Figura 1):

·         Verificação sintática dos construtores SQL embutidos;

·         Verificação de data type do Java e SQL;

·         Verificação do schema.

 

picture12.JPG

Figura 1. Funcionamento do SQLJ.

Começando a trabalhar com o SQLJ

Neste momento você pode estar se perguntando: "o que preciso para trabalhar com esta ferramenta?". Você pode usar qualquer banco de dados Oracle a partir da versão 8. Lembre-se de que o banco de dados tem que ter a JVM (Java Virtual Machine) instalada.

Para este artigo, usarei o Oracle 8i e nos exemplos de codificação você notará que a conexão e parte do código utilizam bibliotecas Oracle. Na Listagem 1 temos uma sequence e uma tabela que serão usadas neste artigo 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.

 

Além do seu banco de dados, você precisará das seguintes bibliotecas adicionais para transformar o SQLJ: translator.jar, runtime12.jar (Oracle library).

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).

 

1. import java.sql.*;    // este import é necessário para o SQLException

2.                       // e outras classes do JDBC

3. import oracle.sqlj.runtime.Oracle;

4. public class SingleRowQuery extends Base {

5.    public static void main(String[] args) {

6.       try {

7.          connect();

8.          singleRowQuery(1);

9.       } catch (SQLException e) {

10.         e.printStackTrace();

11.      }

12.   }

13.   public static void singleRowQuery(int id) throws SQLException {

14.      String fullname = null;

15.      String street = null;

16.      //OUT é o default para as variáveis host INTO

17.      #sql {

18.         SELECT fullname,

19.            street INTO : OUT fullname,

20.            : OUT street FROM customer WHERE ID = :id};

21.      System.out.println("Customer with ID = " + id);

22.      System.out.println();

23.      System.out.println(fullname + " " + street);

24.   }

25.}

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 (ler Nota 1) 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.

 

Nota 1. 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 (ver linhas 17 a 20):

 

   #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.

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().

 

Eis aqui o formato para criar um iterator em SQLJ:

 

   #SQL iterator << iterator name >>

      (<< list of attributes declarations) };

 

E o formato para instanciar 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);

 

// 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() throws SQLException {

      //declarando o objeto iterator que armazenará o resultado

 

      CustomerItr itr;    // Instanciando o iterator

      //populando o iterator utilizando uma declaração SELECT

 

      #sql itr = { SELECT id, fullname,street FROM customer };

      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();

      }

   //fechando o iterator

 

   itr.close();

}

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.

Conclusão

Escrever código enxuto e claro usando um backend 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.