Início > Programação > Mapeamento de Oracle Types em Java

Mapeamento de Oracle Types em Java

Uma das vantagens da orientação a objetos é a reutilização de código. Em PL/SQL, o equivalente à esse aspecto da orientação a objetos é o uso de tipos. Os tipos permitem a separação de interfaces e detalhes de implementação. Os tipos encapsulam estruturas de dados e as funções e procedures que as manipulam.

Vamos explicar o funcionamento de cada estrutura necessárias através da implementação de uma procedure que retorna um array de tipos Pessoa. Primeiro, vamos criar uma tabela para armazenar os dados das pessoas.

CREATE TABLE TBL_PESSOA
(
  PK     NUMBER(3) NOT NULL,
  NOME   VARCHAR2(64) NOT NULL,
  IDADE  NUMBER(2) NOT NULL
);

E em seguida vamos inserir alguns registros para validar a procedure e o tipo que serão criados.

INSERT INTO TBL_PESSOA
VALUES (1,'JOSÉ', 30);
INSERT INTO TBL_PESSOA
VALUES (2,'JOÃO', 35);

Vamos definir a interface do tipo TYPE_PESSOA. Note a função CONSTRUIR designada como STATIC. Métodos STATIC, diferente de métodos MEMBER, devem ser ivocados no tipo e não em uma instância. Esse método é apenas um facilitador para criar instâncias do tipo.

CREATE OR REPLACE TYPE TYPE_PESSOA AS OBJECT (
   PK    NUMBER(3),    -- Identificador
   NOME  VARCHAR2(64), -- Nome
   IDADE NUMBER(2),    -- Idade
   STATIC FUNCTION CONSTRUIR RETURN TYPE_PESSOA
);

Em seguida, vem a implementação ou BODY do tipo. Na implementação do tipo definimos o comportamento da função CONSTRUIR. Ela apenas instancia TYPE_PESSOA com valores nulos. Essa instância será preenchida na procedure que será definida posteriormente.

CREATE OR REPLACE TYPE BODY TYPE_PESSOA AS
   STATIC FUNCTION CONSTRUIR RETURN TYPE_PESSOA IS
     REC TYPE_PESSOA;
   BEGIN
     REC := TYPE_PESSOA(NULL,NULL,NULL);
     RETURN REC;
   END CONSTRUIR;
END;

Como nosso retorno será um array de registros de Pessoas, vamos definir um tipo auxiliar que armazenará TYPE_PESSOA em tabela durante a execução da procedure. Sendo assim, TYPE_PESSOA_LISTA é um array de TYPE_PESSOA.

CREATE OR REPLACE TYPE TYPE_PESSOA_LISTA AS
TABLE OF TYPE_PESSOA;

A última estrutura de banco de dados que precisaremos implementar é a procedure. Essa procedure busca pessoas e popula um TYPE_PESSOA para cada ocorrência e as armazena no tipo auxiliar TYPE_PESSOA_LISTA. Nessa implementação, usei labels e loops para navegar pelos registros, mas assumo que se você precisou criar um tipo, que é uma estrutura complexa, deve conhecer bem os camandos básicos, tratamento de erros – que não mostrei aqui – e como funciona a declaração de variáveis e atribuição de valores em PL/SQL.

 
CREATE OR REPLACE PROCEDURE PROCEDURE_BUSCAR_USUARIOS
   (P_USUARIO_LISTA OUT NOCOPY TYPE_PESSOA_LISTA)
IS

V_PK NUMBER(3);
V_NOME VARCHAR2(64);
V_IDADE NUMBER(2);

BEGIN
   V_PK := 0;
   V_NOME := '';
   V_IDADE := 0;
   P_USUARIO_LISTA := TYPE_PESSOA_LISTA();

   <<LABEL_PESSOA>>;
   FOR V_PESSOA IN (
      SELECT TP.PK, TP.NOME, TP.IDADE
         INTO V_PK, V_NOME, V_IDADE
         FROM TBL_PESSOA TP
      )LOOP

     V_PK := V_PESSOA.PK;
     V_NOME := V_PESSOA.NOME;
     V_IDADE := V_PESSOA.IDADE;
      
     P_USUARIO_LISTA.EXTEND(1);
     P_USUARIO_LISTA(P_USUARIO_LISTA.COUNT) := TYPE_PESSOA.CONSTRUIR();
     P_USUARIO_LISTA(P_USUARIO_LISTA.COUNT).PK := V_PK;
     P_USUARIO_LISTA(P_USUARIO_LISTA.COUNT).IDADE := V_IDADE;
     P_USUARIO_LISTA(P_USUARIO_LISTA.COUNT).NOME := V_NOME;
   END LOOP LABEL_PESSOA;

END PROCEDURE_BUSCAR_USUARIOS;

Agora vamos passar para a implementação do código Java. Comecemos pela definição de um POJO Pessoa.

public class Pessoa {
   private Integer id;
   private String nome;
   private Integer idade;
   // getters e setters
   public Pessoa(Integer id, Integer idade, String nome) {
      this.id = id;
      this.idade = idade;
      this.nome = nome;
   }
}

A interface SQLData é utilizada para implementar um mapeamento mais refinado e granular de um tipo de dado definido no banco de dados – não necessariamente aquele tipo que criamos – para uma classe Java. A implementação do SQLData receberá um SQLInput, que é um objeto entregue pelo driver do banco de dados e que permite acesso ou gravação de valores. Vamos definir uma classe que implementa um SQLData para facilitar a extração das informações que virão da procedure por meio daquele array de tipos que definimos.

import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;

public class PessoaOracleType implements SQLData {
   private int id;
   private String nome;
   private int idade;

   private String sqSQLDatal_type;

   public String getSQLTypeName() {
      return sql_type;
   }

   public int getId() {
      return id;
   }

   public String getNome() {
      return nome;
   }

   public int getIdade() {
      return idade;
   }

   public void readSQL(SQLInput stream, String type) 
      throws SQLException {
      sql_type = type;
      id = stream.readBigDecimal().intValue();
      nome = stream.readString();
      idade = stream.readBigDecimal().intValue();
   }

   public void writeSQL(SQLOutput stream) 
      throws SQLException {
      stream.writeInt(id);
      stream.writeString(nome);
      stream.writeInt(idade);
   }
}

Vamos utilizar a interface Work do Hibernate para ter acesso à Connection que será utilizada para ivocarmos a procedure criada. Uma classe curiosa que vai aparecer nessa implementação é a STRUCT, que é fornecida pelo driver do Oracle. Uma instância de STRUCT é a materialização de um objeto Oracle quando não há um mapeamento Java/SQL explícito. No nosso caso, cada linha do array de tipos retornado pela chamada da procedure será uma STRUCT.


import java.sql.Array;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.sql.rowset.serial.SQLInputImpl;

import oracle.jdbc.OracleTypes;
import oracle.sql.STRUCT;

import org.hibernate.Session;
import org.hibernate.jdbc.Work;

public class Procedure {
   private List<Pessoa> pessoas;
   private Session session;

   public Procedure(Session session) {
      this.session = session;
   }

   public List<Pessoa> getPessoas() {
      return pessoas;
   }

   public void execute() {
      session.doWork(new Work() {
         @Override
         public void execute(Connection con) throws SQLException {
            CallableStatement callStm = null;
            try {
               Map<String, Class<?>> map = con.getTypeMap();
               map.put("TYPE_PESSOA_LISTA", PessoaOracleType.class);
               callStm = con.prepareCall(
                 "{call PROCEDURE_BUSCAR_USUARIOS(?)}");
               registrarParametroSaida(callStm, "P_USUARIO_LISTA", 
                 OracleTypes.ARRAY, "TYPE_PESSOA_LISTA");
               callStm.execute();
               pessoas = extrair(callStm);
            } finally {
               if (callStm != null) {
                  callStm.close();
               }
            }
         }
      });
   }

   private void registrarParametroSaida(CallableStatement cs, 
     String nomeParametro, int oracleTypes, String typeName)
     throws SQLException {
     cs.registerOutParameter(nomeParametro, oracleTypes, typeName);
   }

   @SuppressWarnings("unchecked")
   private List<Pessoa> extrair(CallableStatement cs) 
      throws SQLException {
      List<Pessoa> pessoas = new ArrayList<Pessoa>();
      Array array = cs.getArray("P_USUARIO_LISTA");
      ResultSet rs = array.getResultSet();

      while (rs.next()) {
         STRUCT row = (STRUCT) rs.getObject(2);
         PessoaOracleType vot = new PessoaOracleType();
         SQLInput input = 
           new SQLInputImpl(row.getAttributes(), row.getMap());
         vot.readSQL(input, row.getSQLTypeName());
         Pessoa pessoa = 
           new Pessoa(vot.getId(), vot.getIdade(), vot.getNome());
         pessoas.add(pessoa);
      }
      return pessoas;
   }
}

Por fim, implemente um método no DAO para fazer a chamada da procedure.


@Stateless
public class PessoaDao
{
   @PersistenceContext(unitName = "pu")
   private EntityManager em;

   private Session getSession(){
      return (Session) em.getDelegate();
   }

   public List<Pessoa> listar() {
      Procedure procedure = new Procedure(getSession());
      procedure.execute();
      return procedure.getPessoas();
   }
}

Conclusão

Lidar com estruturas de banco de dados e com esses tipos do Oracle em especial é particularmente difícil para um desenvolvedor de software, mas tenha em mente que esses problemas se apresentam de vez em quando e você, como engenheiro de software e não um mero desenvolverdor, deve encontrar a melhor solução dadas as restrições do projeto como as que discuti aqui.

Referências

1. [https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/objects.htm]
2. [https://docs.oracle.com/cd/B28359_01/appdev.111/b28371/adobjbas.htm]
3. [https://docs.oracle.com/javase/7/docs/api/java/sql/SQLData.html]
4. [https://docs.oracle.com/javase/tutorial/jdbc/basics/sqlcustommapping.html]
5. [https://docs.oracle.com/javase/7/docs/api/java/sql/SQLInput.html]
6. [https://docs.oracle.com/cd/A87860_01/doc/java.817/a83724/oraoot3.htm]
7. [https://docs.oracle.com/cd/A87860_01/doc/java.817/a83724/oraoot1.htm]
8. [http://docstore.mik.ua/orelly/oracle/prog2/ch18_02.htm]
9. [http://blog.mclaughlinsoftware.com/2012/02/14/how-to-use-object-types/]
10. [https://docs.oracle.com/cd/B13789_01/appdev.101/b10807/10_objs.htm]
11. [https://docs.oracle.com/cd/B28359_01/appdev.111/b28371/adobjplsql.htm#i7530]
12. [https://docs.oracle.com/cd/B19306_01/appdev.102/b14261/objects.htm#i20461]

Anúncios
  1. Nenhum comentário ainda.
  1. No trackbacks yet.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

%d blogueiros gostam disto: