13

Click here to load reader

Artigo - Camadas de Persistencia de Objetos

  • Upload
    herval

  • View
    1.496

  • Download
    1

Embed Size (px)

Citation preview

Page 1: Artigo - Camadas de Persistencia de Objetos

Castor JDOPersistência Fácil de Objetos em JavaHERVAL FREIRE DE A. JÚNIOR

As camadas de persistência facilitam o desenvolvimento de aplicações com acesso a banco de dados. Aprenda a usar a API Castor através de um exemplo simples.

A biblioteca Castor é um framework de databinding em Java: em termos simples, ela permite transformação descomplicada entre objetos Java, XML, diretórios LDAP e bancos de dados relacionais com expressiva facilidade.

Atualmente na versão 0.9x, a biblioteca Castor vem sendo utilizada em um grande número de projetos de sistemas J2EE: O servidor de aplicações open-source JBoss, por exemplo, utiliza-se da API de persistência do Castor na sua implementação de CMP (Container Managed Persistence) de EJBs.

A API JDO (Java Data Objects)Um dos pacotes que compõem a biblioteca Castor é o pacote org.exolab.jdo – um conjunto de

classes com a função de gerenciar a persistência de objetos Java em bancos de dados relacionais. Através das classes deste pacote, o programador pode definir mapeamentos entre suas classes Java e tabelas em bancos de dados relacionais, de forma bastante semelhante ao mapeamento feito para EntityBeans, no contexto dos EJBs.

Para realizar a ligação (binding) entre as classes de Java e tabelas do banco de dados, o Castor utiliza-se de arquivos de configuração no formato XML: estes descritores associam os atributos e relacionamentos das classes a campos e referências nas tabelas do banco. Esta associação, além de informar ao Castor como realizar a persistência dos atributos, também servirá para abstrair a camada de banco de dados na hora de realizar consultas, como veremos no decorrer deste tutorial.

A inserção, atualização e recuperação de objetos persistentes é um processo simples, realizado em apenas uma linha de código – um grande atrativo para aqueles que se acostumaram a realizar operações em banco de dados utilizando longas instruções SQL inseridas no meio do código das classes ou em objetos de acesso a dados (DAOs).

Além de garantir esta facilidade de inclusão e alteração de dados, a API JDO fornece também uma linguagem de consulta utilizada para manipulação de objetos: trata-se de um sobconjunto da linguagem de consulta para bancos de dados orientados a objetos OQL (Object Query Language), de sintaxe muito similar à SQL. Como o Castor trabalha com persistência de dados em bancos de dados relacionais, a biblioteca trata de mapear consultas OQL em SQL padrão, de forma que o programador não precisa mais se preocupar em descrever joins complexos entre tabelas, referência explícita a chaves de tabelas, etc.

Nota: Apesar do nome, o pacote JDO do Castor não implementa a especificação JDO da Sun em sua totalidade. É importante lembrar que muitos recursos, especialmente no que diz respeito à linguagem de consulta OQL, ainda estão em desenvolvimento no projeto.

Aplicação Modelo: Sistema de Controle de Funcionários

Cenário da aplicaçãoPara exemplificar alguns dos recursos do pacote JDO, criaremos uma pequena aplicação. As

classes utilizadas no exemplo seguem a especificação mostrada na figura 1. É válido lembrar que o objetivo deste esquema de classes não é ser fiel a nenhuma realidade nem representa a melhor estratégia de modelagem de classes para a situação dada: ele apenas demonstra os tipos de

1

Page 2: Artigo - Camadas de Persistencia de Objetos

relacionamentos comuns na modelagem Orientada a Objetos - relacionamentos 1-para-muitos, 1-para-1 e muitos-para-muitos e relacionamentos simples. Além disso, a presença de atributos de diversos tipos serve para dar uma idéia da capacidade de conversão de tipos da API de persistência do pacote Castor JDO.

Funcionario

codigo : intnome : Stringfoto : byte[]salario : doublegratificacao : doublenascimento : Datetelefone : String

Setor

codigo : intnome : Stringtelefone : String

0..10..* 0..10..*

0..*0..* 0..*

+gerente

0..*

+operario

Figura 1. Esquema UML da aplicação modelo

O primeiro passo para que possamos trabalhar com a persistência das classes no banco de dados é criar um esquema físico de armazenamento. Para que a persistência seja possível, o esquema mostrado na figura 2 foi criado no banco de dados. Para os testes, foi utilizada uma base de dados MySQL – um dos SGBDs suportados pelo Castor.

Figura 2. Esquema de Banco de Dados da aplicação de exemplo

Classes utilizadas no exemploO código-fonte das classes utilizadas neste exemplo é comum a qualquer desenvolvedor Java e

pode ser visto na listagem 1. Para cada atributo privado, supõe-se a existência de métodos públicos get/set, segundo o padrão JavaBean.

Listagem 1: Código-fonte das classes da aplicação modelo

Package example;

class Setor { private String nome; private String telefone; private Computador computadores;

2

Page 3: Artigo - Camadas de Persistencia de Objetos

private int codigo; private java.util.Vector funcionarios; private java.util.Vector gerentes;}

class Funcionario {

private String nome; private java.util.Date nascimento; private double salario; private String telefone; private byte[] foto; private Setor setor; private TimeFutebol[] times; private int codigo; private double gratificacao;}

Modelagem do BDO script da listagem 2 gera a as tabelas necessárias para os testes.

Listagem 2: Script de geração de tabelas no banco de dadoscreate database empresaBD;use empresaBD;

create table tb_func ( codigo int not null primary key auto_increment, foto blob, nasc date, name varchar(50), salario double, setor int not null references tb_setor, fone varchar(12), gratificacao double);

create table tb_setor ( codigo int not null primary key auto_increment, nome varchar(50), fone varchar(12));

create table tb_gerente_setor ( cod_gerente int not null references tb_func, cod_setor int not null references tb_setor, primary key(cod_gerente, cod_setor));

Uma vez que temos toda esta estrutura disponível, podemos partir para o primeiro passo do processo de configuração do ambiente Castor.

Passo 1: conectando ao banco de dadosAntes que qualquer dado possa ser gravado ou recuperado de uma base de dados, é necessário que

o desenvolvedor descreva a ligação (binding) entre as tabelas e objetos do sistema. Além de relacionar classes com tabelas, também é necessário especificar o banco de dados utilizado, driver, senha para conexão e outros detalhes.

Tais configurações são feitas mediante a criação de um descritor XML que deve ser carregado na aplicação para a localização correta dos dados. A listagem 3 mostra os passos necessários para a carga de um descritor e a preparação para utilização do banco de dados com o pacote JDO.

Listagem 3: Inicializando uma configuração de banco de dados

Database db;JDO jdo = new JDO();jdo.setDatabaseName("empresa_db");jdo.setConfiguration("database.xml");

3

Page 4: Artigo - Camadas de Persistencia de Objetos

db = jdo.getDatabase();

db.begin(); // inicializa transação// faça alguma coisa aquidb.commit(); // encerra transaçãodb.close(); // fecha conexão com o banco

Neste código, informamos ao objeto JDO que as configurações de dados encontram-se no arquivo “database.xml”. Ao executar o método getDatabase(), o objeto JDO procurará a configuração relativa ao banco de dados de nome “empresa_db”. Após recuperar o objeto Database, o programador precisa iniciar uma transação para que possa trabalhar os objetos e então executar quaisquer operações livremente. A persistência dos dados somente é realizada na chamada ao método commit(). Caso seja necessário, existe também um método “rollback()” na classe Database, o que permite que a transação seja desfeita em caso de problemas no fluxo de execução.

Conectando a um banco de dados em aplicações J2EEA API CASTOR dá suporte, também, a conexões com bancos em servidores de aplicação J2EE utilizando JNDI. Após disponibilizar o objeto JDO com um nome JNDI, o código para recuperar uma conexão de acesso ao banco em uma aplicação J2EE típica é simples, como mostra o trecho de código abaixo:

InitialContext ctx;UserTransaction ut;Database db;

ctx = new InitialContext();db = (Database) ctx.lookup(“java:comp/env/jdo/empresa_db”);

// faça alguma coisa aqui

db.close();

Como na maioria dos casos a transação é gerenciada pelo container, o programador não precisa especificar seu início e fim explicitamente. Alguns containers open-source vem utilizando o Castor desta forma, como framework de persistência de EJBs.

No nosso exemplo específico, para que o banco de dados possa ser aberto e utilizado, o descritor database.xml tem o conteúdo mostrado na listagem 4:

Listagem 4. Arquivo database.xml

<!DOCTYPE databases PUBLIC "-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN" "jdo-conf.dtd"> <database name="empresa_db" engine="mysql"> <driver class-name="org.gjt.mm.mysql.Driver" url="jdbc:mysql://localhost/empresaBD"> <param name="user" value="herval" /> <param name="password" value="herval" /> </driver> <mapping href="tabelas.xml" /> </database>

A interpretação deste arquivo é bastante simples e direta. O exemplo demonstra as configurações para um banco de dados de nome “empresa_db”, explicitando também a base de dados em que estamos trabalhando – neste caso, um banco de dados MySQL – e determina o driver de acesso e os parâmetros necessários para a conexão.

Por último, o elemento <mapping>, que pode aparecer várias vezes, define os mapeamentos entre classes e tabelas no sistema. No nosso exemplo, a única instância da elemento <mapping> faz referência ao arquivo externo “tabelas.xml”. Este arquivo será explorado mais a diante, no decorrer do exemplo, e servirá para descrever a ligação (binding) entre tabelas e classes.

Tipos de bancos de dados suportados pelo CastorNo arquivo “database.xml” do nosso exemplo, vemos que o atributo “engine” recebeu o valor “mysql”. Este valor determina como o Castor gerará código SQL para acesso às tabelas do sistema, assim como

4

Page 5: Artigo - Camadas de Persistencia de Objetos

determina a existência ou não de alguns recursos, como veremos no decorrer do tutorial. O atributo “engine” pode receber, atualmente, os valores mostrados na tabela 1, a seguir

Banco de dados Atributo engineBase de dados JDBC genérica genericOracle 7 ou 8 oracleSybase 11 sybaseSQL Server sqlserverDB/2 db2Informix informixPostgreSQL 7.1 postgresqlInstantDB instantdbInterbase interbaseSAP DB sapdbMySQL mysql

Tabela 1: Bancos de dados suportadas pelo Castor e valores do atributo “engine”

Passo 2: Mapeando objetos em tabelasPara que a persistência e recuperação de dados sejam possíveis, precisamos estabelecer algum

tipo de relacionamento entre as classes do sistema e as tabelas do banco. A estrutura do descritor de relacionamentos merece atenção especial, neste nosso pequeno tutorial. Antes de iniciarmos o exemplo real proposto, tomaremos algum tempo para conhecer a fundo esta estrutura. O arquivo descritor de mapeamentos segue a estrutura mostrada na listagem 5 a seguir: para cada classe a ser mapeada, um elemento <class> deve ser escrito e configurado adequadamente.

Listagem 5. Esqueleto de um descritor de mapeamentos

<!DOCTYPE databases PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN" "mapping.dtd"><mapping> <class> … </class>

<class> … </class>

</mapping>

O elemento <class> tem sua estrutura mostrada na listagem 6. Para descrever e especificar cada atributo da classe específica, o elemento <class> comporta um número arbitrário de elementos <field>, um para cada atributo mapeado para um campo na tabela.

Listagem 6. DTD do elemento XML <class>

<!ELEMENT class ( description?, cache-type?, map-to?, field+ )> <!ATTLIST class name ID #REQUIRED extends IDREF #IMPLIED depends IDREF #IMPLIED auto-complete ( true |false ) "false" identity CDATA #IMPLIED access ( read-only | shared | exclusive | db-locked ) "shared" key-generator IDREF #IMPLIED >

Explicando cada um dos atributos do descritor XML de uma classe, temos:name – o nome completo da classe mapeada, incluindo pacote.extends – nome da classe-pai, em caso de herança. Apenas aplicado se a classe-pai também tiver

um descritor de mapeamento relacionado com ela.

5

Page 6: Artigo - Camadas de Persistencia de Objetos

depends – utilizado para descrever classes dependentes, no caso de relacionamentos de agregação.

auto-complete – em caso de “true”, o Castor localiza atributos na classe que não possuem mapeamento e tenta persisti-los automaticamente.

identity – define qual campo é chave primária de identificação de objetos da classe. Em caso de chaves compostas, o nome dos atributos é separado com espaços.

access – determina a forma de acesso a objetos persistentes desta classe.key-generator – define qual gerador de chave será utilizado, caso um seja adotado. Os geradores

de chave permitem ao Castor gerar o código de identificação no objeto utilizando recursos proprietários de cada banco, como Stored Procedures, geradores de OIDs/UUIDs, algoritmos MAX e High/Low, utilizando seqüências (Sequences) ou campos auto-incrementais. Por tratar-se de configurações avançadas e de certa forma simples de entender em cada caso, omitiremos explicações mais detalhadas sobre este atributo no decorrer deste tutorial.

Os filhos do elemento <class> são descritos a seguir:description – uma descrição textual da classe e do mapeamentocache-type – define se será utilizado ou não o armazenamento em memória dos objetos

armazenados do bancomap-to – elemento que define “para onde” a classe deve ser mapeada. Como estamos estudando

apenas persistência para bancos de dados relacionais, esta tag terá sempre o formato <map-to table=“nome_da_tabela” />. Apesar de não estudado neste tutorial, é possível definir também mapeamentos para XML e LDAP neste atributo.

field – os elementos “field” são os descritores dos atributos individuais da classe a ser mapeada. A estrutura de uma tag <field> é mostrada na listagem 7.

Listagem 7. DTD do elemento XML <field><!ELEMENT field ( description?, sql?, xml?, ldap? )> <!ATTLIST field name NMTOKEN #REQUIRED type NMTOKEN #IMPLIED required ( true | false ) "false" direct ( true | false ) "false" lazy ( true | false ) "false" transient ( true | false ) "false" get-method NMTOKEN #IMPLIED set-method NMTOKEN #IMPLIED create-method NMTOKEN #IMPLIED collection ( array | vector | hashtable | collection | set | map ) #IMPLIED>

Uma breve explicação dos atributos do elemento <field> segue:name – nome do atributo da classe mapeada. Caso o atributo “direct” seja omitido ou definido

como “false”, o objeto será acessado através dos métodos get/set relacionados a ele: Um atributo “codigo” que seja acessado através dos métodos getCode() e setCode(), por exemplo, e defina direct=“false” deve mapear o atributo name=“code”. Caso direct esteja definido como “true”, o atributo deve ser name=“codigo”. Neste caso, o acesso através dos métodos get/set não é feito. É importante lembrar que o atributo deve ter visibilidade pública (public), para tal acesso.

type – tipo do atributo em Java. Os tipos de objetos aceitos para o mapeamento incluem qualquer tipo de objeto Java. Para facilitar a utilização de alguns tipos mais comuns, o pacote Castor define um pequeno grupo de nomes curtos, mostrado na Tabela 2. Mesmo no caso de coleções (arrays, vectors, hashtables), este atributo deve ser configurado com o tipo dos elementos da coleção. Para definir que um dado atributo é na realidade uma coleção, o atributo “collection” deve ser utilizado.

required – indica se o atributo é requerido (não-nulo) ou nãolazy – atribuir “true” a este atributo faz com que os objetos só sejam carregados quando

requerido. Para tal, o atributo “collection” deve existir.transient – se indicado como “true”, indica que este campo deve ser ignorado durante a

persistência/recuperação

6

Page 7: Artigo - Camadas de Persistencia de Objetos

get-method – determina o nome do método getter do atributo, caso a assinatura não siga o padrão da especificação JavaBean (public <tipo> get<Atributo>())

set-method - determina o nome do método setter do atributo, caso a assinatura não siga o padrão da especificação JavaBean (public void set<Atributo>(<tipo> valor))

collection – determina o tipo, no caso de um atributo que seja uma coleção. As coleções possíveis são arrays, Vectors, Hashtables, ArrayLists, HashSets e HashMaps do tipo definido pelo atributo “type”. O valor recebido por este atributo para cada caso é, respectivamente, “array”, “vector”, “hashtable”, “collection”, “set” e “map”.

Tabela 2: Nomes curtos para mapeamento de atributos JavaA API Castor define um conjunto de nomes curtos para facilitar a especificação do tipo de atributos em um mapeamento. Os nomes aceitos são enumerados na tabela a seguir:

Nome Curto Classe Javaother java.lang.Objectstring java.lang.Stringinteger java.lang.Integer.TYPE (int)long java.lang.Long.TYPE (long)boolean java.lang.Boolean.TYPE (boolean)double java.lang.Double.TYPE (double)float java.lang.Float.TYPE (float)big-decimal java.math.BigDecimalbyte java.lang.Byte.TYPE (byte)date java.util.Dateshort java.lang.Short.TYPE (short)char java.lang.Character.TYPE (char)bytes byte[]chars char[]strings String[]locale java.util.Localestream java.io.InputStream

Dos elementos internos da tag <field>, apenas o elemento <sql> é interessante para nosso estudo atual. A sua estrutura é mostrada na listagem 8.

Listagem 8. DTD do elemento XML <sql>

<!ELEMENT sql EMPTY> <!ATTLIST sql name NMTOKEN #REQUIRED type NMTOKEN #IMPLIED many-key NMTOKEN #IMPLIED many-table NMTOKEN #IMPLIED read-only ( true | false ) "false" dirty ( check | ignore ) “check” >

Os atributos do elemento <sql> têm o seguinte significado:name – o nome da coluna no banco de dadostype – o tipo do atributo no banco de dados – um nome literal dentre os tipos listados na tabela 3.

No caso de relacionamentos com outras classes, o atributo type deve ser omitido. Desta forma, o Castor buscará pela chave primária na tabela relacionada para realizar a persistência do dado. Para atributos do tipo char, é permitido um mecanismo de mapeamento especial. Supondo que se queira mapear um atributo booleano para um banco de dados de forma que sejam armazenados os caracteres “S” para representrar “true” e “N” para não, podemos utilizar o seguinte exemplo de configuração:

Listagem 9. Exemplo de mapeamento utilizando máscara (valor booleano).

<field name=“meuAtributo” type=“boolean”> <sql name=“atributoAtivo” type=“char[NS]”/>

7

Page 8: Artigo - Camadas de Persistencia de Objetos

</field>

Datas também podem ser mascaradas para armazenamento no banco de dados, em casos onde o formato de armazenamento do banco seja diferente do formato fornecido pelo método toString() da classe Date. O exemplo da listagem 10 ilustra um exemplo de formatação para armazenar datas no formato “dd/mm/yyyy”

Listagem 10. Exemplo de mapeamento utilizando máscara (data).

<field name=“minhaData” type=“date”> <sql name=“atributoData” type=“char[dd/MM/yyyy]”/></field>

many-key – atributo utilizado para relacionamentos 1-para-n ou n-para-n: indica o nome da chave estrangeira que identifica unicamente objetos do tipo dado por “type” que se relacionam com a classe que contém esta definição.

many-table – nome da tabela intermediária, no caso de relacionamentos n-para-n normalizados que utilizam uma tabela para relacionar os objetos.

Tabela 3: Tipos SQL e tipos Java relacionadosPara permitir a armazenarem e recuperação correta de atributos no banco de dados, o Castor permite a utilização dos seguintes tipos sql como valores possíveis para o parâmetro “type” do mapeamento de campos:

Tipo SQL Tipo Java

bigint java.lang.Long

binary byte[]

bit java.lang.Boolean

blob java.io.InputStream

char java.lang.String

clob java.sql.Clob

date java.sql.Date

decimal java.math.BigDecimal

double java.lang.Double

float java.lang.Double

integer java.lang.Integer

longvarbinary java.lang.String

numeric java.math.BigDecimal

real java.lang.Float

smallint java.lang.Short

time java.sql.Time

timestamp java.sql.Timestamp

tinyint java.lang.Byte

varbinary byte[]

varchar java.lang.String

Mapeando a classe FuncionarioTerminadas todas estas explicações a respeito da estrutura do mapeamento de uma classe, é hora

de prosseguirmos com nosso exemplo. Mapeamos a classe Funcionario à tabela tb_func utilizando o descritor de classe mostrado na listagem 11.

Listagem 11. Descritor de ligação entre a classe Funcionario e a tabela tb_func

<class name="example.Funcionario" identity="codigo" key-generator="IDENTITY"> <map-to table="tb_func" />

<field name="codigo" type="integer">

8

Page 9: Artigo - Camadas de Persistencia de Objetos

<sql name="codigo" type="integer"/> </field>

<field name="foto" type="bytes"> <sql name="foto" type="blob"/> </field>

<field name="nascimento" type="date"> <sql name="nasc" type="char[yyyy-MM-dd]"/> </field>

<field name="nome" type="string"> <sql name="name" type="char"/> </field>

<field name="salario" type="double"> <sql name="salario" type="double"/> </field>

<field name="setor" type="example.Setor"> <sql name="setor" /> </field>

<field name="setoresGerenciados" type="example.Setor" collection="collection"> <sql name="cod_setor" many-key="cod_gerente" many-table="tb_gerente_setor" /> </field>

<field name="telefone" type="string"> <sql name="fone" type="char"/> </field> </class>

Podemos identificar, neste nosso caso, exemplos de aplicação de formatadores para data (atributo “nascimento”), um relacionamento um-para-muitos (atributo “setor”), um relacionamento muitos-para-muitos (atributos “setoresGrenciados”) e diversos outros atributos mapeados para tipos diversos no banco de dados SQL. Dentre os atributos mapeados, o mais incomum é o nosso relacionamento n-para-n com a classe Setor (atributo “setoresGerenciados”): este especifica o atributo “many-table” com o nome da tabela que relaciona gerentes a setores e o atributo “name” da tag <sql> como o campo chave que identifica setores na tabela “tb_gerente_setor”.

O mapeamento da classe Setor é ainda mais simples e pode ser visto na listagem 12.

Listagem 12. Mapeamento da classe Setor

<class name="example.Setor" identity="codigo" key-generator="IDENTITY"> <map-to table="tb_setor" />

<field name="codigo" type="integer"> <sql name="codigo" type="integer" /> </field>

<field name="gerentes" type="example.Funcionario" collection="vector"> <sql name="cod_gerente" many-key="cod_setor" many-table="tb_gerente_setor" /> </field>

<field name="funcionarios" type="example.Funcionario" collection="vector"> <sql many-key="setor" /> </field>

<field name="nome" type="string"> <sql name="nome" type="char"/> </field>

<field name="telefone" type="string"> <sql name="fone" type="char"/> </field> </class>

Após escrever estes mapeamentos, podemos finalmente começar a trabalhar com as classes em Java normalmente, sem preocupações maiores com utilização de SQL para persistência e recuperação de objetos. Graças à camada de persistência, todo o código de acesso ao banco que precise ser gerado será criado e executado automaticamente, como veremos nos exemplos a seguir.

9

Page 10: Artigo - Camadas de Persistencia de Objetos

Passo 3: Brincando de JavaBeansApós todo o trabalho inicial de mapeamento de objetos para tabelas, a manipulação de objetos é

transparente. O código demonstrado na listagem 13 a seguir demonstra diversas operações realizadas sobre um conjunto de objetos. Ao final, teremos objetos armazenados nas tabelas de nosso banco de dados sem ter escrito nenhuma linha de código utilizando JDBC.

Listagem 13. Exemplos

Outros recursos da API de persistência

<<interface Persistence>>

<<OQL>>

<<DIRTY CHECKING>>

ConclusõesA utilização de uma camada de persistência para interação com o banco de dados trás uma série

de vantagens, especialmente visíveis no desenvolvimento de aplicações de maior porte.A remoção de acessos a banco de dados utilizando SQL inserido no meio do código da aplicação,

a facilidade de modificação dos mapeamentos em tempo de deployment e a possibilidade de configurar detalhes de armazenamento dos dados em um arquivo de configuração são apenas alguns exemplos dos benefícios apresentados pelo Castor e em outras APIs de persistência de objetos. Sem dúvida, a adoção de camadas de persistência como um elemento arquitetural durante o desenvolvimento de aplicações garante produtividade, qualidade e confiabilidade de código.

Referênciashttp://www.exolab.org – site oficial do projeto Castorhttp://Jakarta.apache.org/ojb – site do projeto OJB, uma camada de persistência compatível com a especificação ODMGhttp://www.AmbySoft.com/ - artigos sobre construção de camadas de persistência de objetoshttp://hibernate.sourceforge.net - API Hibernate de persistência para bancos de dados relacionais

Herval Freire de A. Júnior ([email protected]) é graduado em Telemática pelo CEFET-PB, programador e desenvolvedor de componentes web certificado pela Sun. Atualmente, trabalha como programador Java na Politec - João Pessoa.

10