Você está na página 1de 31

Apache Cayenne: Persistncia de dados

Fique por dentro


Este artigo til por apresentar o Apache Cayenne, um mapeador objeto-relacional no aderente
especificao JPA. Integrado a uma poderosa ferramenta grfica o Cayenne Modeler esta soluo da
Apache agrega agilidade e simplicidade ao desenvolvimento da camada de persistncia de dados.
Sendo assim, desenvolvedores que buscam solues voltadas para a persistncia de dados podem encontrar
no Apache Cayenne uma eficiente alternativa de ORM que abstrai qualquer necessidade de lidar com
arquivos XML ou anotaes durante o trabalho de mapeamento.
A necessidade de persistir dados em tempo de execuo to antiga quanto a prpria computao. De fato, a
abordagem de gerenciar dados persistentes tem sido uma pea fundamental de deciso na concepo de todo
projeto de software.
Notadamente, a aplicabilidade de um sistema cujos dados manipulados no sejam salvaguardados em algum
formato ou estrutura contemplaria um escopo to reduzido que a prpria existncia do sistema poderia ser
objeto de questionamento.
Na perspectiva da plataforma Java, a persistncia normalmente est associada ao armazenamento de dados
em bancos de dados relacionais que fazem uso da linguagem SQL. No contexto deaplicaes orientadas a
objetos, a persistncia possibilita que um objeto sobreviva finalizao do processo que o criou, uma vez
que seu estado pode ser armazenado em disco, o que viabiliza a sua posterior recriao.
Naturalmente essa operao no limitada a apenas um nico objeto todo um grafo de objetos pode da
mesma forma ser persistido e subsequentemente recriado por meio de um novo processo.
Atualmente, grande parte dos projetos de softwares corporativos fazem uso conjugado do paradigma da
orientao a objetos para o desenvolvimento de aplicaes e da persistncia em bancos de dados
relacionais para o armazenamento das informaes.
Contudo, as diferenas existentes entre os fundamentos dessas duas abordagens inviabilizam uma perfeita
adequao no uso concomitante de ambas. Isto porque enquanto o paradigma da orientao a objetos
baseado em princpios advindos da Engenharia de Software, o paradigma relacional, por outro lado,
baseado em fortes princpios matemticos.
Logo, para que as duas abordagens possam coexistir em um mesmo projeto de sistema, preciso preencher
essa lacuna semntica (conhecida como Impedance Mismatch) que separa os dois paradigmas.
Uma tcnica encontrada para contornar as especificidades que separam o mundo dos objetos do mundo das
relaes ficou conhecida como mapeamento objeto-relacional (Object-Relational Mapping ORM). Em
poucas palavras, um mapeamento objeto-relacional um mecanismo de persistncia baseado na traduo de
objetos de uma aplicao para tabelas de um banco de dados relacional por meio de metadados que
descrevem o mapeamento entre os objetos e o banco. Em essncia, uma soluo de ORM consiste em quatro
peas principais:
Uma API para executar operaes bsicas de CRUD (Create-Read-Update-Delete) sobre objetos de
classes persistentes;
Uma linguagem ou API para especificar queries que possam referenciar classes e suas respectivas
propriedades;
1

Uma forma de se construir mapeamentos por meio de metadados;


Um mecanismo para que a implementao do ORM possa interagir com objetos transacionais a fim de
executar a otimizao de funes, como checagem de inconsistncia de dados ou recuperao lazy de
associaes.
Alguns dos benefcios que podem ser derivados do uso de um mecanismo de ORM so:
Produtividade: Boa parte do cdigo relacionado com a camada de persistncia de uma aplicao
substituda pelas funcionalidades da soluo de ORM. Logo, desenvolvedores podem focar ainda mais nos
problemas referentes ao negcio para o qual a aplicao est sendo construda;
Manutenibilidade: Uma menor quantidade de linhas de cdigo torna um sistema mais simples de se
compreender, uma vez que o cdigo existente est focado na lgica de negcio. Ainda mais importante o
fato de que sistemas com menos linhas de cdigo so mais fceis de serem refatorados.
Assim, dado que a persistncia automatizada por uma soluo de ORM reduz a quantidade de linhas de
cdigo de um sistema, ento dela pode-se obter os ganhos citados anteriormente;
Desempenho: Aclama-se que um cdigo de persistncia escrito diretamente pelo desenvolvedor da
aplicao tem um desempenho pelo menos igual e, com frequncia pode ter um desempenho superior,
quando comparado ao cdigo gerado e executado por um mecanismo de automatizao de persistncia.
Contudo, quando uma tarefa de persistncia analisada, sabe-se que diversas otimizaes so passveis de
serem efetuadas. Algumas delas talvez sejam mais simples de implementar por meio de cdigos construdos
pelos prprios programadores.
Porm, muitas dessas otimizaes so mais fceis de serem alcanadas por meio de solues automatizadas.
Alm disso, equipes que implementam ORM provavelmente j desprenderam muito mais tempo
investigando mtricas de desempenho do que o tempo que qualquer desenvolvedor teria para estudar
possveis otimizaes;
Independncia de fornecedores: Um mecanismo de ORM abstrai da aplicao as particularidades do
banco de dados e do dialeto SQL usados. Se a ferramenta suporta um nmero diferente de bancos de dados,
isso confere um certo nvel de portabilidade aplicao.
Logo, o desenvolvimento de um sistema multiplataforma torna-se usualmente mais simples quando h a
utilizao de uma soluo de ORM. Alm disso, ainda que operaes multiplataforma no constituam um
requisito para o sistema, o ORM pode auxiliar na mitigao de alguns dos riscos associados dependncia
de fornecedores especficos.
Diante dos benefcios advindos da automatizao das operaes de persistncia, diversas implementaes de
mecanismos de ORM surgiram. Na perspectiva da tecnologia Java, boa parte delas segue a especificao
JPA.
Dentre os mais conhecidos esto Hibernate, TopLink e EclipseLink. Neste artigo, ser apresentado uma
soluo de ORM que no segue a especificao JPA, o Apache Cayenne. Para exemplificar o uso da
ferramenta, esta ser utilizada para implementao da camada de persistncia de uma aplicao web de
exemplo.

Nota: A JPA (Java Persistence API) foi desenvolvida por um grupo de especialistas em EJB 3.0 como parte
da JSR 220, porm no est limitada a componentes EJB. Ela prov um modelo de persistncia de POJO que
d suporte ao desenvolvimento de aplicaes independentes de solues particulares de ORM.
O Apache Cayenne
O Apache Cayenne um framework open source que prov um mecanismo de mapeamento objetorelacional, alm de servios remotos auxiliares. Iniciado em 2001 e desde ento, mantido pela comunidade,
o projeto Cayenne possibilita a reduo do tempo de desenvolvimento de aplicaes cuja persistncia
baseada em modelos relacionais, uma vez que cria uma abstrao orientada a objetos das estruturas
existentes em esquemas de bancos de dados.
Sua verso estvel mais recente a 3.0.2 (verso usada neste artigo). Porm, conta ainda com uma release
candidate verso 3.1 e uma opo mais atual que encontra-se em fase de testes, a verso 3.2. Algumas
das suas principais caractersticas so:
Suporte tanto engenharia reversa quanto gerao de bancos de dados;
Engine de gerao de classes baseada no Velocity;
Mecanismo de caching;
Completo conjunto de objetos voltados para a sintaxe de queries;
Herana de objetos;
Autodeteco de banco de dados;
Capacidade de escalar projetos de qualquer dimenso para ambientes virtuais.
Notadamente, diversas caractersticas implementadas pelo Cayenne so comuns a outros mecanismos de
ORM. Contudo, um relevante diferencial disponibilizado nesta soluo a possibilidade de ter todas as suas
funes controladas diretamente via uma ferramenta com interface grfica o CayenneModeler.
De fato, esta ltima isenta o desenvolvedor da necessidade de lidar com quaisquer arquivos XML ou
anotaes durante a etapa de mapeamento objeto-relacional. Ressaltando ainda mais o potencial da
ferramenta, por meio do CayenneModeler possvel que um completo esquema de banco de dados seja
mapeado diretamente para objetos Java via interface grfica.
Outra caracterstica que distingue o framework Cayenne dos demais mecanismos de ORM relaciona-se com
a sua arquitetura, a qual pode ser vista como a composio de duas grandes solues de persistncia:
Cayenne Persistence API: API de propsito geral para mapeamentos objeto-relacionais. Internamente
utiliza arquivos XML para descrio dos mapeamentos, porm estes arquivos podem ser abstrados do
desenvolvedor por meio da utilizao da interface do CayenneModeler;
Remote Object Persistence (ROP): uma tecnologia que habilita o uso da API de persistncia do
Cayenne em aplicaes-cliente remotas. Um uso comum do ROP oferecer a aplicaes SWT ou Swing um
web service para acesso aos dados, em substituio ao acesso direto ao banco de dados.
Em casos como esses, um modelo abstrato de dados compartilhado entre os dois lados cliente e servidor.

Alm do mecanismo de persistncia propriamente dito, o Cayenne disponibiliza funcionalidades voltadas


para o gerenciamento dos objetos persistveis:
Execuo de queries: queries podem ser invocadas a fim de recuperar objetos que atendam a certos
critrios. Outro aspecto que objetos persistentes podem ser criados e preenchidos com valores provenientes
do banco de dados;
Chamada a um nico mtodo de Commit e Rollback: mltiplos objetos persistentes podem ter suas
alteraes gravadas no banco de dados por meio de uma nica chamada de commit e dentro de uma nica
transao;
Mltiplos nveis de Commit e Rollback aninhados: As funcionalidades de commit e rollback podem ter
mltiplos nveis de aninhamento, isto , um contexto pode executar o rollback de suas modificaes sem
afetar o contexto-pai.
De forma anloga, a execuo do commit das alteraes de um contexto no persiste no banco de dados as
modificaes do contexto-pai;
Relacionamentos: O suporte a relacionamentos possibilita que objetos referenciados por um objeto
recuperado possam ser acessados via uma nica chamada simples de mtodo. Por default, os
relacionamentos no so recuperados juntos com os objetos.
Logo, em um relacionamento TO-MANY, os objetos referenciados so recuperados quando o tamanho da
lista solicitado ou quando o usurio tenta acessar um de seus elementos;
Gerenciamento automtico de relacionamentos bidirecionais: Se uma entidade A tem um
relacionamento com a entidade B e, alm disso, a entidade B tem um relacionamento com a entidade A, o
Cayenne mantm a consistncia do relacionamento reverso automaticamente.
A Listagem 1 d uma viso do efeito que esse gerenciamento tem sobre o cdigo. Naturalmente, esse
recurso simplifica o gerenciamento de complexos grafos de objetos;
Listagem 1. Teste unitrio para demonstrao do gerenciamento de relacionamentos bidirecionais efetuado
pelo Cayenne.
Revista jm;
Artigo cayenne;
Artigo CayenneModeler;
jm.setArtigo(cayenne);
assertTrue(cayenne.getListOfRevista().contains(jm));
jm.setArtigo(CayenneModeler);
assertTrue(CayenneModeler.getListOfRevista().contains(jm));
assertFalse(CayenneModeler.getListOfRevista().contains(jm));

Injeo de Contexto: O framework Cayenne executa a atribuio de todas as trs propriedades (objectId,
persistenceState e objectContext) definidas na interface Persistence em momentos adequados do ciclo de
vida dos objetos. Isso automatiza a manuteno dos estados de persistncia quando um objeto sofre alguma
transformao em seu estado;
Nota: O Cayenne capaz de persistir objetos Java que implementem a interface
org.apache.cayenne.Persistent. Essa interface uma extenso da interface java.io.Serializable requer
que cada objeto proveja os mtodos acessores (get/set) para trs propriedades do bean: objectId,
persistentState e objectContext.
4

Exclusividade de identidade: o Cayenne assegura que cada ObjectContext contenha, no mximo, uma
instncia de cada objeto persistente. Em outras palavras, se duas queries independentes requisitam um
registro com a mesma chave primria, a mesma instncia do objeto ser usada para ambos os resultados.
Este comportamento (no suportado por alguns frameworks) extremamente importante para a manuteno
da consistncia do grafo de objetos;
Nota: ObjectContext uma interface que define a API para a aplicao interagir com o Cayenne. As duas
implementaes mais usadas so: DataContext, usada na maioria das aplicaes Cayenne; e
CayenneContext, usada em clientes remotos.
Recuperao lazy de objetos: Para objetos que so recuperados via relacionamentos, o Cayenne executa
um carregamento do tipo lazy dos seus dados, isto , apenas a chave-primria (objectId) recuperada e
atribuda.
Contudo, a qualquer momento, quando este tipo de objeto for manipulado pelo usurio (um mtodo set/get
invocado), o Cayenne automaticamente popula os demais atributos com os dados do banco.
Ciclo de vida dos objetos
O ciclo de vida de objetos persistentes no Cayenne pode ser representado por um diagrama dos estados
possveis dos objetos e das transies que podem existir entre eles.
Transies entre estados ocorrem em resposta interao da aplicao com os objetos persistentes
modificao de atributos, por exemplo ou com o prprio contexto de persistncia do Cayenne incluso e
remoo de objetos do contexto.
A Figura 1 apresenta as mudanas de estado que um novo objeto pode sofrer dentro do contexto do
Cayenne. J a Figura 2 descreve as transies possveis para objetos recuperados de um banco de dados ou
salvos nele.
No contexto de persistncia do Cayenne, so definidos seis estados possveis para um objeto:
TRANSIENT: o objeto no est registrado no contexto de persistncia e no est persistido na base;
NEW: o objeto foi recentemente criado no contexto de persistncia, porm no est persistido na base (no
tem um registro no banco correspondente);
COMMITED: o objeto est registrado no contexto de persistncia e j est persistido na base (h um
registro no banco correspondente);
MODIFIED: o objeto est registrado no contexto de persistncia e tem um registro correspondente na
base de dados. Porm, o objeto foi modificado na memria e suas alteraes no foram persistidas na base;
HOLLOW: o objeto est registrado no contexto de persistncia e tem um registro correspondente na base.
Porm, os valores ainda no foram recuperados do banco. Os valores sero automaticamente carregados pelo
Cayenne se alguma propriedade do objeto for requisitada;
DELETED: o objeto est registrado no contexto de persistncia e ser removido da base aps o prximo
commit.

Figura 1. Transies de um novo objeto no contexto de persistncia do Cayenne.

Figura 2. Transies de objetos persistentes dentro do contexto do Cayenne.


Gerenciamento de chaves primrias
As diferentes estratgias de gerao de chaves-primrias geralmente esto associadas aos respectivos bancos
de dados existentes. E como o conceito de chave-primria um aspecto de persistncia do modelo relacional
6

que alcana o mundo dos objetos, essa diversidade de estratgias tem impacto nas abordagens de gerao de
PKs implementadas pelos mecanismos de ORM. Algumas das abordagens disponveis no Cayenne so:
Chaves primrias de negcio: Nessa abordagem, objetos persistveis pelo Cayenne tm um subconjunto
de seus atributos utilizados como chave primria. Essa estratgia pode ser especialmente til quando a chave
primria tem significado no domnio do negcio. Neste caso, a PK tratada pelo Cayenne da mesma forma
que qualquer outro atributo persistente;
Chaves primrias derivadas de relacionamentos: A chave primria de uma coluna pode depender da
chave de outra tabela. Esse cenrio geralmente ocorre em casos nos quais join tables so usados para
resolver relacionamentos MANY-TO-MANY. Nestes casos, o DataObject normalmente no contm uma
propriedade mapeada para uma coluna de chave primria.
Ao contrrio, o valor da chave automaticamente derivado pelo Cayenne a partir da chave primria do
objeto referenciado pelo relacionamento;
Chaves primrias providas por bancos de dados: H casos em que o banco de dados dotado do seu
prprio mecanismo de gerao de chaves primrias quando um novo registro inserido.
Este aspecto conhecido como auto incremento ou coluna identidade. O Cayenne suporta este tipo de
provimento externo de chave primria;
Chaves primrias geradas pelo Cayenne: Na maioria dos casos, a chave primria puramente um
conceito relacional que no tem correspondente no modelo de objetos. Normalmente ela constitui apenas um
nmero sequencial que identifica um registro no banco de dados.
Logo, a criao de um atributo num objeto persistente a fim de corresponder chave primria pode ser vista
como um passo artificial no processo de mapeamento. Para isentar os desenvolvedores dessa
responsabilidade, o Cayenne define para cada objeto um atributo ObjectId, o qual gerenciado pelo prprio
mapeador. Dessa maneira, os desenvolvedores ficam isentos da responsabilidade de lidar diretamente com a
chave primria no modelo de objetos da aplicao.
Nota: DataObjects so objetos passveis de serem persistidos pelo Cayenne. Objetos desse tipo so
compostos por atributos e relacionamentos. Os atributos dos DataObjects so simplesmente as propriedades
dos objetos (como propriedades de um bean Java) que podem ser mapeadas para colunas de um banco de
dados.
Por sua vez, os relacionamentos representam outros DataObjects que so referenciados por um objeto
persistvel. Na prtica, um relacionamento tambm constitui uma propriedade do DataObject, porm cujo
tipo outro DataObject (ou coleo de DataObjects). Assim, tanto atributos como relacionamentos podem
ser lidos ou modificados atravs da invocao dos mtodos get/set correspondentes.
Queries
No Cayenne, a manipulao dos dados da base feita por meio de objetos Java que abstraem o dialeto SQL
usado pelo gerenciador de banco de dados. As principais classes que implementam essa abstrao e
disponibilizam queries para propsitos gerais nas aplicaes so:
SelectQuery: uma classe Java que implementa o comando SELECT da linguagem SQL. Todos os
parmetros do comando so informados como atributos da classe. Para a maioria das tarefas com o Cayenne,
recomendvel consider-la como a primeira opo para a implementao das queries;

SQLTemplateQuery: uma classe Java que possibilita a execuo de queries escritas apenas com
recursos da linguagem SQL. Em sua maioria, o uso de SQLTemplateQuery voltado para a criao de
queries complexas, as quais no so possveis de serem construdas via modelo de objetos. Esse tipo de
objeto possibilita ainda a adaptao do cdigo SQL para diferentes dialetos de bancos de dados;
ProcedureQuery: Adicionalmente ao mapeamento de tabelas e vises de bancos de dados para entidades
do modelo de objetos, o Cayenne permite o mapeamento e execuo de stored procedures.
Essa funcionalidade complementar disponibilizada por meio da classe ProcedureQuery;
EJBQLQuery: Classe Java que d suporte implementao de queries cuja sintaxe segue o padro
EJBQL descrito na especificao JPA.
Alm das principais queries mencionadas, o Cayenne oferece um conjunto de classes para simplificar ainda
mais a manipulao dos dados da base. Algumas dessas classes so as seguintes:
QueryChain: Classe Java que referencia um conjunto de outras queries. O objetivo dessa classe
possibilitar a execuo de mltiplas queries em uma nica chamada ao banco de dados.
ObjectIdQuery: Classe Java que possibilita a recuperao de objetos baseando-se apenas no atributo
ObjectId. Naturalmente, como o ObjectId sempre nico para cada objeto, o resultado obtido pela classe
ObjectIdQuery composto de um nico objeto ou nulo, quando nenhum registro encontrado no banco.
Estrutura geral de um projeto Cayenne
A implementao de uma camada de persistncia baseada no Cayenne demanda a manipulao de estruturas
que implementam conceitos especficos do framework. Essas estruturas do suporte construo tanto do
modelo relacional como do modelo de objetos, bem como criao do mapeamento entre eles. Os principais
elementos do Cayenne com essa finalidade so:
DataDomain: Constitui uma abstrao de um data source lgico, podendo contemplar mltiplos bancos
de dados fsicos. A relao de todos os DataDomains presentes num projeto pode ser encontrada no arquivo
cayenne.xml;
DataNode: um objeto que corresponde a um nico data source fsico, normalmente um banco de dados.
Atualmente existem dois tipos de DataNodes:
o O baseado em um data source, obtido via JNDI;
o E outro baseado em um driver JDBC. Neste caso, o Cayenne prov seu prprio pool de conexes e outras
funcionalidades esperadas de um data source.
DataMap: uma coleo de informaes de mapeamentos objeto-relacionais que vincula objetos Java a
tabelas e vises de um banco de dados relacional. De fato, um DataMap contm o mapeamento objetorelacional propriamente dito, ou seja, nele onde so definidas as correspondncias entre objetos e
estruturas do banco de dados;
Entities: No Cayenne, tanto elementos do mundo dos objetos como elementos do mundo relacional so
tratados como Entities, ou seja, tanto classes Java como tabelas e vises do banco de dados so considerados
entidades. Existem, porm, dois subtipos de Entity, os quais so descritos nos DataMaps. So eles:
o ObjEntity: Entity que representa a estrutura de uma classe persistente Java;
8

o DbEntity: Entity que contm a representao da estrutura de uma tabela/viso do banco de dados.
Vale notar que cada ObjEntity baseado em um DbEntity, porm podem existir DbEntities que no tm
um ObjEntity correspondente;
Embeddables: um tipo especial de objeto persistente que permite o mapeamento de partes de uma tabela
para um objeto separado, possibilitando, dessa forma, o uso de composies dentro de entidades.
A Figura 3 mostra uma viso geral de como um projeto Cayenne estruturado. Esse esquema, que abrange
trs conceitos centrais do Cayenne DataDomain, DataNode e DataMap constitui uma abstrao dos trs
tipos de arquivos que so usados para configurar o mecanismo de ORM numa aplicao. A seguir, h uma
descrio desses arquivos (a varivel $DataDomainName usada para representar o nome do projeto
Cayenne):
cayenne.xml: Constitui o arquivo-raiz do Cayenne em cada aplicao. Nele so configurados quais os
DataDomains e DataNodes que devem ser reconhecidos pelo framework;
{$DataDomainName}Map.map.xml: Arquivo usado para instanciar objetos DataMap, os quais descrevem
o esquema do banco de dados e seu mapeamento para as classes Java da aplicao;
{$DataDomainName}Node.driver.xml: Arquivo que prov as informaes necessrias para conexo com o
banco de dados: URL, JDBC driver, usurio, senha e parmetros do pool de conexes.

Figura 3. Estruturas bsicas de um projeto Cayenne.


O CayenneModeler
Embora os arquivos de configurao gerados por um projeto Cayenne possam ser editados manualmente, o
CayenneModeler oferece uma interface grfica em Java que dispensa essa necessidade de interao direta
com os arquivos XML.
Alm disso, possibilita que todo o processo de mapeamento objeto-relacional possa ser executado sem
qualquer contato com arquivos ou anotaes. Outras funcionalidades do CayenneModeler so:
Engenharia reversa de esquemas de banco de dados convertendo-os para DataMaps;
Gerao de esquemas de bancos de dados a partir de informaes de mapeamentos armazenadas em
DataMaps;
9

Gerao de classes Java a partir das informaes presentes em DataMaps;


Validao e criao de mapeamentos.
Diante das facilidades oferecidas pelo CayenneModeler, este constitui uma poderosa ferramenta que agrega
simplicidade e agilidade atividade de mapeamento objeto-relacional. Para obt-lo, acesse a pgina oficial
do Apache Cayenne (veja a seo Links). Uma vez baixado o arquivo .zip do Cayenne, o executvel do
CayenneModeler se encontra no diretrio bin gerado aps a descompactao.
A chamada ao arquivo CayenneModeler.exe disponibiliza a interface principal da ferramenta, a qual
apresentada na Figura 4.

Figura 4. Interface inicial do CayenneModeler.


A aplicao Amanaje
Com o objetivo de consolidar os conceitos apresentados do Apache Cayenne e de observar como o
framework pode ser empregado em sistemas web, foi desenvolvida uma aplicao chamada de Amanaje
cuja interface disponibiliza um conjunto simplificado de funcionalidades.
Como o intuito dar uma viso geral do uso do ORM oferecido pelo Cayenne, apenas um CRUD (CreateRead-Update-Delete) foi implementado na aplicao. Alm disso, para manter o foco centrado no mapeador,
alguns critrios de arquitetura de sistemas e boas prticas no desenvolvimento de software foram abstrados.
Nota: Segundo o pesquisador Gerson Frana, a palavra amanaj (de origem tupi) significa reunio, ou
ajuntamento. Com base nesse conceito, o objetivo da aplicao Amanaje o de reunir um conjunto bsico
de funcionalidades a fim de demonstrar o uso da persistncia de dados via Apache Cayenne.
10

A ideia geral da aplicao Amanaje a de uma interface para manuteno de um cadastro de usurios e de
suas respectivas qualificaes. Nesse sentido, foram projetadas duas classes para compor o modelo de
objetos:
Usuario: representa o prprio usurio cujas qualificaes sero associadas;
TipoQualificacao: representa os diferentes nveis de qualificao que um usurio pode ter.
Como o relacionamento entre as duas classes do tipo M:N, foi desenhado para a aplicao um modelo de
entidade-relacionamento que fizesse uso de uma tabela auxiliar a Join Table qualificacao a fim de
agregar as chaves das duas entidades, conforme apresentado na Figura 5.
Tratando-se da arquitetura geral da aplicao, foi feita uma organizao do cdigo em trs pacotes:
dao: Pacote que contm os Data Access Object da aplicao;
model: Pacote que agrupa o modelo de objetos;
web.beans: Pacote onde so encontrados os controladores das pginas web.

Figura 5. Diagrama de Entidade-Relacionamento da aplicao Amanaje.


Para fins de simplificao tanto da gerncia de dependncias quanto da construo das pginas web, a
aplicao foi criada como um projeto Maven fazendo uso do framework JSF 2.1. Para a persistncia dos
dados, utilizou-se o gerenciador de banco de dados PostgreSQL, verso 8.4.
Alm disso, para deploy e execuo da aplicao, utilizou-se o JBoss AS 6.1. A Listagem 2 apresenta o
cdigo do arquivo pom.xml relacionando as bibliotecas demandadas pela aplicao.
Listagem 2. Arquivo pom.xml da aplicao Amanaje.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>br.jm</groupId>
<artifactId>amanaje</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>com.sun.faces</groupId>
<artifactId>jsf-api</artifactId>
<version>2.1.7</version>
<scope>provided</scope>
</dependency>
<dependency>

11

<groupId>com.sun.faces</groupId>
<artifactId>jsf-impl</artifactId>
<version>2.1.7</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.cayenne</groupId>
<artifactId>cayenne-server</artifactId>
<version>3.0RC2</version>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901.jdbc4</version>
</dependency>
</dependencies>
</project>

Vale observar nesta listagem que as duas dependncias referentes ao JSF tm escopo provided, uma vez que
ambas sero fornecidas pelo container em tempo de execuo. E no caso da biblioteca que fornece o
mecanismo de persistncia do Cayenne, esta referenciada pelo artifactId cayenne-server. Finalmente, h
uma dependncia que aponta para o driver JDBC do banco de dados utilizado.
Mapeamento ORM com o CayenneModeler
De posse do modelo de entidade-relacionamentos da aplicao, j possvel avanar ao mapeamento objetorelacional. Naturalmente, em ambientes corporativos nos quais o nvel de complexidade dos sistemas
demandam modelos muito mais elaborados a execuo da etapa de ORM seguramente demandaria um
diagrama de classes em representao ao modelo de negcio.
No exemplo em questo, porm, dado a simplicidade do escopo envolvido, os objetos sero projetados
medida que o modelo relacional for incorporado ferramenta grfica de modelagem.
Diante disso, como passo inicial ser realizada a criao do DataDomain da aplicao a raiz de todo
projeto de mapeamento no CayenneModeler. Para isso, deve-se acessar o menu File > New Project e, em
seguida, indicar o nome do DataDomain (utilizou-se o nome AmanajeDomain para a aplicao Amanaje).
Em seguida, ao efetuar o salvamento do novo projeto, a ferramenta solicitar a indicao do diretrio no
qual o arquivo cayenne.xml ser criado. Assim, dado que esse arquivo ser utilizado posteriormente pela
aplicao, importante que o diretrio informado faa parte da sua estrutura. No caso da aplicao
Amanaje,
foi
utilizado
o
caminho
$AMANAJE_HOME/src/main/webapp/WEB-INF/cayenne
($AMANAJE_HOME corresponde ao caminho absoluto at o diretrio onde o cdigo-fonte da aplicao se
encontra).
No contexto do Cayenne,um DataNode corresponde a um data source fsico; logo, para a aplicao
Amanaje, criou-se o AmanajeDataNode, contendo as informaes para acesso ao banco de dados.
Isso foi feito subsequentemente criao do DataDomain, atravs do menu Project >DataNode
(opcionalmente, pode-se ter acesso a essa funcionalidade via barra de ferramentas boto Create
DataNode). Uma vez executada essa operao, disponibilizada uma tela na qual os dados de configurao
do data source devem ser informados.
Alm disso, preciso indicar qual a estratgia de atualizao do esquema do banco a ser adotada. Neste
artigo, ser delegado ao Cayenne a responsabilidade de criao e atualizao do esquema.

12

Logo, a classe selecionada no campo Schema Update Strategy deve ser CreateIfNoSchemaStrategy (no
caso de no permitir que o Cayenne faa atualizaes no esquema, deve-se selecionar a classe
SkipSchemaUpdateStrategy).
Aps o salvamento das operaes (criao e configurao do DataNode e configurao do data source), o
CayenneModeler ir criar o arquivo AmanajeDomainNode.driver.xml no mesmo diretrio indicado para o
arquivo cayenne.xml.
Observe que a criao desse segundo arquivo na estrutura da aplicao faz-se necessria dado que as
informaes contidas nele sero usadas posteriormente para se obter acesso aos dados do banco.
Nota: A tela de criao de DataNodes do CayenneModeler adicionalmente exige que seja indicada a classe
responsvel pela instanciao do data source configurado. Isso informado no campo DataSource Factory
da tela. Para aplicaes que faam uso de data sources gerenciados por um container e obtidos via chamada
JNDI, a classe (factory) a ser utilizada a JNDIDataSourceFactory. Por outro lado, quando o data source
de responsabilidade da prpria aplicao, a classe adequada a DriverDataSourceFactory (opo default
do CayenneModeler e utilizada neste artigo).
Outro conceito j abordado foi o de DataMap, o qual se refere ao mapeamento objeto-relacional
propriamente dito. Para o exemplo em desenvolvimento, h a necessidade de criao de um DataMap que
agregue os mapeamentos das classes Usuario e TipoQualificacao para as tabelas usuario, qualificacao e
tipo_qualificacao.
Essa etapa foi efetuada acessando-se o menu Project > Create DataMap (opcionalmente, pode-se ter acesso
a essa funcionalidade via barra de ferramentas boto Create DataMap), o qual disponibiliza uma tela
cujos seguintes campos devem ser preenchidos:
DataMap Name: Refere-se ao nome do DataMap criado (para a aplicao em questo, utilizou-se
AmanajeDomainMap).
DataNode: Campo para seleo do DataNode envolvido no mapeamento;
DB Schema: Campo onde deve ser indicado o esquema do banco de dados a ser usado pelo mapeamento
(definiu-se um esquema com nome amanaje para o exemplo do artigo);
Java Package: Corresponde ao pacote da aplicao Java que conter as classes geradas pelo
CayenneModeler a partir dos mapeamentos construdos. No caso da aplicao Amanaje, foi indicado o
pacote br.jm.amanaje.model.
Aps o preenchimento dos referidos campos, o salvamento das configuraes efetuadas acarretar na
gerao de um terceiro arquivo AmanajeDomainMap.map.xml no mesmo diretrio utilizado para criao
dos dois arquivos anteriores.
Alm disso, com base na informao de pacote indicada nesta tela de configurao do DataMap, o
CayenneModeler adicionar estrutura da aplicao dois novos pacotes:
br.jm.amanaje.model: Nesse pacote, a ferramenta ir gerar as classes correspondentes aos mapeamentos
de objetos construdos atravs da sua interface. Essas classes no vo conter qualquer cdigo.
Na realidade, elas constituiro apenas extenses de outras classes que tambm sero geradas pelo
CayenneModeler e que efetivamente contero a implementao Java do mapeamento objeto-relacional.
Neste pacote da aplicao Amanaje estaro presentes as classes Usuario e TipoQualificacao;
13

br.jm.amanaje.model.auto: Pacote que conter as classes que vo implementar o mapeamento objetorelacional propriamente dito. Todas as classes desse pacote tambm so geradas automaticamente pela
ferramenta e jamais devero ser alteradas.
Qualquer necessidade de implementao de lgica no modelo de objetos deve ser efetuada nas subclasses
presentes no pacote indicado na interface do CayenneModeler (br.jm.amanaje.model).
Uma particularidade presente nas classes desse pacote est no fato de todas terem o prefixo _ no nome. No
caso da aplicao Amanaje, o pacote conter as classes _Usuario e _TipoQualificacao.
A Figura 6 apresenta a tela do CayenneModeler aps a criao das estruturas iniciais do projeto
DataDomain, DataNode e DataMap e configurao do AmanajeDomainNode. A Figura 7 exibe a
estrutura da aplicao Amanaje dentro do Eclipse.

Figura 6. Tela de configurao do DataNode no CayenneModeler.

14

Figura 7. Projeto da aplicao Amanaje.


Uma vez concludas as configuraes iniciais do projeto no CayenneModeler, o prximo passo envolve a
construo dos mapeamentos objeto-relacionais propriamente ditos. Dado que a gerao do banco de dados
foi delegada ao CayenneModeler (como apresentado na configurao do DataNode), iniciou-se o trabalho de
mapeamento pela modelagem do esquema do banco, em conformidade com o diagrama da Figura 5.
Alm disso, como j mencionado anteriormente, uma tabela do banco de dados representada no Cayenne
pelo objeto DbEntity. Logo, para a criao da tabela USUARIO, deve-se selecionar o
AmanajeDomainMap com o mouse e acionar a opo Create DbEntity (ou pelo menu Project > Create
DbEntity).
Aps a atribuio do nome usuario ao novo objeto, so definidos os seus atributos. Para isso, basta acionar
o boto Create Attribute, localizado na aba Attributes que surge aps a seleo do DbEntity recm-criado.
Os valores das propriedades do atributo (Name, Type, PK, Mandatory, Max Length e Scale) so preenchidos
em seguida. Esta sequncia de passos deve ser repetida para cada uma das tabelas do banco.
A Figura 8 apresenta a tela do CayenneModeler aps a gerao dos trs DbEntity da aplicao Amanaje
(USUARIO, QUALIFICACAO, TIPO_QUALIFICACAO) e detalha os atributos do DbEntity usuario.
15

Figura 8. Tabelas da aplicao Amanaje modeladas no CayenneModeler.


Nota: Durante todo o processo de mapeamento com o CayenneModeler, recomenda-se que o projeto seja
periodicamente salvo, especialmente porque o Eclipse no consegue identificar as alteraes no projeto at
que estas tenham sido efetivamente salvas.
De posse do modelo de tabelas da aplicao criado no CayenneModeler, preciso avanar para a construo
dos relacionamentos entre essas tabelas. Como dito anteriormente e apresentado na Figura 5, nossa
aplicao demanda a criao de relacionamentos entre as tabelas USUARIO, QUALIFICACAO e
TIPO_QUALIFICACAO. Comeando com o relacionamento ONE-TO-MANY entre USUARIO e
QUALIFICACAO, a seguinte sequncia de passos deve ser executada no CayenneModeler:
Selecione o DbEntity USUARIO e depois a aba Relationships direita;
Clique no boto Create Relationship para criar um relacionamento, e o renomeie para qualificacoes (o
nome default gerado pelo CayenneModeler para um relacionamento untitledRel);
Selecione a tabela Target QUALIFICACAO, a qual representa o lado MANY do relacionamento;
Clique no boto Database Mapping (boto cujo cone uma letra I dentro de um crculo). Essa ao
disponibilizar uma caixa de dilogo para a configurao do relacionamento;
Utilize o boto Add da caixa de dilogo para as colunas usadas na operao de join. Para o relacionamento
entre USUARIO e QUALIFICACAO foram selecionadas as colunas que contm as chaves primrias de
ambas as tabelas, ou seja, USUARIO.id e QUALIFICACAO.id_usuario. Neste ponto, a caixa de dilogo do
relacionamento USUARIO-QUALIFICACAO deve estar de acordo com a Figura 9;
Aps a confirmao das mudanas, a caixa de dilogo fechada e dois relacionamentos complementares
so criados: de USUARIO para QUALIFICACAO e vice-versa (no caso de relacionamentos bidirecionais).
Contudo, de volta aba Relationships, embora o relacionamento criado seja do tipo ONE-TO-MANY, o
checkbox To Many no est marcado. Este checkbox possibilita a indicao de qual o sentido TOMANY do relacionamento (neste caso, se do DbEntity USUARIO para o DbEntity QUALIFICACAO, ou
vice-versa).
Logo, por se tratar de um relacionamento com apenas um lado MANY, este checkbox deve ser marcado na
aba Relationships do DbEntity USUARIO. E nessa mesma aba, porm do DbEntity QUALIFICACAO, o
checkbox deve ser desmarcado.

16

Figura 9. Configurao do relacionamento USUARIO QUALIFICACAO.


A mesma sequncia de passos descrita deve ser executada para o outro relacionamento ONE-TO-MANY,
entre TIPO_QUALIFICACAO e QUALIFICACOES.
De posse do esquema do banco de dados completamente mapeado, pode-se utilizar o CayenneModeler para
gerar as classes Java (ObjEntities) a partir de derivaes de cada DbEntity. Para gerao das classes da
aplicao Amanaje, o seguinte passo-a-passo deve ser executado:
Selecione o DbEntity usuario e clique no boto Create ObjEntity (boto cujo cone composto por uma
letra C dentro um crculo verde). Essa ao far com que um ObjEntity com nome Usuario seja criado
dentro do DomainMap.
E aps o salvamento do projeto, a classe Java Usuario adicionada ao pacote br.jm.amanaje.model
(pacote que foi definido na criao do AmanajeDomainMap). Alm disso, o CayenneModeler transforma os
nomes dos atributos do DbEntity usuario em atributos na classe Java correspondente;
Selecione o DbEntity tipo_qualificacao e clique mais uma vez no boto Create ObjEntity. Assim, um
ObjEntity TipoQualificacao gerado. Esse mesmo processo deve ser repetido para o DbEntity qualificacao.
Para finalizar a etapa de mapeamento, preciso executar uma sincronizao dos relacionamentos dos
ObjEntities criados. Isso se faz necessrio porque Usuario e TipoQualificacao foram gerados antes do
ObjEntity Qualificacao, e com isso, os respectivos relacionamentos no foram adequadamente
configurados. Para essa etapa final, realize a seguinte sequncia de passos:
Selecione o ObjEntity Usuario e depois clique na aba Relationships;
Clique no boto Sync ObjEntity with DbEntity (boto cujo cone tem setas amarelas). A execuo dessa
ao deve fazer com que o relacionamento qualificacoes seja exibido na aba Relationships;
Repita esses passos para o ObjEntity TipoQualificacao.
Uma vez finalizado o mapeamento das duas classes da aplicao, vale ressaltar o fato de estas serem
direcionadas para trs tabelas do banco de dados, conforme apresentado pelo esquema da Figura 10. Isso
17

porque essa situao traz tona um conceito implementado pelo Cayenne conhecido como relacionamento
flattened.
Como j mencionado, no caso da nossa aplicao, a terceira tabela (qualificacao) refere-se a uma join table
criada para tratar o relacionamento MANY-TO-MANY entre as classes Usuario e TipoQualificacao. A
indicao desse relacionamento como flattened no mapeamento construdo via CayenneModeler possibilita
que o acesso s instncias de TipoQualificacao por uma instncia de Usuario possa ser feita atravs de uma
chamada direta do tipo usuario.getQualificacoes.
Note
que
no
h
a
necessidade
de
uma
invocao
tal
como
usuario.getQualificacoes.getTipoQualificacoes(). O uso desse mecanismo possibilita que join tables no
precisem ser mapeadas para objetos que no tenham significado algum para o negcio modelado.

Figura 10. Estrutura do mapeamento criado para a aplicao Amanaje.


Nota: Se um relacionamento flattened envolve uma join table e esta tabela est mapeada para um
ObjEntity, ento este ObjEntity pode ser removido do projeto. Contudo, o DbEntity correspondente deve
ser preservado.
Isto porque, embora a join table no tenha significado no modelo de objetos (por isso seu respectivo
ObjEntity foi removido), ela tem significado no modelo relacional. Sendo assim, o DbEntity equivalente
precisa ser mantido.
Para que o relacionamento entre as ObjEntities Usuario, Qualificacao e TipoQualificacao possa ser
configurado como flattened, deve-se executar o seguinte passo-a-passo:
1. Selecione a ObjEntity Usuario e, na aba Relationships, clique no boto Edit Relationship. Com isso, a tela
de dilogo ObjRelationship Inspector ser aberta, apresentando campos para configurao do
relacionamento.
Note que o ObjEntity previamente configurado no campo Target o Qualificacao. Isso significa dizer
que, a partir de um objeto Usuario possvel recuperar instncias de Qualificacao (e no
TipoQualificacao) atravs deste relacionamento;
2. Na rea referente a Mapping to DbRelationships preciso configurar a cadeia de relacionamentos que
conduz classe que se deseja retornar (TipoQualificacao) ao acessar essa relao, a partir da classe
Usuario. Observe que, ao abrir a tela, a configurao presente nessa rea corresponde ao relacionamento
18

qualificacoes, o qual d acesso a instncias da classe Qualificacao (classe esta que representa a join
table).
Logo, para que seja possvel retornar objetos do tipo TipoQualificacao, necessrio modificar o
relacionamento na rea Mapping to DbRelationships. Isso feito simplesmente clicando-se com o mouse
nas reas em branco disponveis nessa seo da tela.
Cada clique ir exibir o prximo relacionamento disponvel. Neste exemplo, foi necessria a adio de
apenas mais uma relao tipo , a qual d acesso classe TipoQualificacao;
3. Clique no boto Select Path e confirme com o boto Done.
A Figura 11 apresenta o estado final da caixa de dilogo aps a configurao do relacionamento flattened
do ObjEntity Usuario.

abrir imagem em nova janela


Figura 11. Caixa de dilogo para configurao do relacionamento flattened.
Gerao de classes e esquema de banco
Uma vez finalizado o trabalho de mapeamento objeto-relacional, e de posse do projeto da aplicao
preparado no Eclipse, possvel proceder para a criao das classes Java e do esquema do banco de dados.
Para a gerao das classes a partir do modelo de ObjEntities construdo, basta selecionar o DomainMap
criado, isto , AmanajeDomainMap (ele fica permanentemente visvel na rvore do projeto, exibida na tela
principal da ferramenta), e acessar a caixa de dilogo Code Generation (composta pelas abas Code
Generator e Classes) por meio do menu Tools > Generate Classes.
Na aba Code Generator deve ser informado o Output Directory correspondente ao diretrio no qual as
classes sero geradas. Naturalmente, este deve ser um diretrio (pacote) da aplicao em desenvolvimento.
Na aba Classes preciso selecionar as classes a serem geradas (como a primeira vez que ser executada
essa operao, todas devem ser selecionadas). A criao efetiva das classes acontece aps o acionamento do
boto Generate da caixa de dilogo aberta.

19

Para gerao do esquema do banco dados, acesse a opo de menu Tools > Generate Database Schema. Se
o data source para o banco da aplicao j tiver sido criado, ento basta referenci-lo no campo Saved
DataSources. Caso contrrio, os dados para a conexo devem ser informados nos campos disponveis.
Aps o acionamento do boto Continue, sero disponibilizadas na mesma caixa de dilogo duas abas: SQL
Options, contendo o script SQL a ser executado no banco informado; e Tables, que indica as tabelas que
sero geradas a partir dos DbEntities modelados no CayenneModeler. Note que duas opes so
apresentadas nessa caixa de dilogo: o boto Save SQL, que possibilita que o script seja salvo; e o boto
Generate, que dispara a execuo do script para gerao do esquema do banco de dados.
Construindo a aplicao Amanaje
Neste ponto do processo de desenvolvimento da aplicao Amanje, j esto finalizadas as seguintes etapas:
Gerao das classes de negcio;
Gerao do banco de dados;
Mapeamento objeto-relacional das classes de negcio para o esquema do banco.
Logo, possvel dar seguimento implementao das camadas de viso e de persistncia da aplicao.
Camada de viso
A implementao da interface com o usurio foi feita com base na tecnologia JavaServer Faces. Para a
utilizao do JSF no projeto, foi necessria a incluso de alguns parmetros no arquivo web.xml. Essa
mesma necessidade se fez presente para a integrao do Cayenne. A Listagem 3 apresenta essas
configuraes includas no web.xml.
Listagem 3. Arquivo web.xml da aplicao Amanaje.
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>amanaje</display-name>
<context-param>
<param-name>cayenne.configuration.path</param-name>
<param-value>/WEB-INF/cayenne</param-value>
</context-param>
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<filter>
<filter-name>CayenneFilter</filter-name>
<filter-class>org.apache.cayenne.conf.WebApplicationContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CayenneFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>

20

</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>

As configuraes demandadas pelo JSF esto associadas ao servlet nomeado de Faces Servlet, o qual
referencia a classe javax.faces.webapp.FacesServlet. Este ser responsvel por tratar todas requisies a
pginas que detenham a extenso .xhtml.
J para a integrao do Cayenne com a aplicao web, necessrio o acrscimo de dois parmetros ao
web.xml:
Indicar o arquivo de configuraes do Cayenne a ser carregado no contexto da aplicao quando esta
iniciada. Isso feito atravs do parmetro cayenne.configuration.path, o qual deve referenciar o diretrio no
qual o arquivo cayenne.xml se encontra;
Indicar o filtro (neste caso, CayenneFilter) responsvel pela associao do DataContext s requisies do
usurio e que referencie a classe org.apache.cayenne.conf.WebApplicationContextFilter. Alm disso,
deve ser definido um mapeamento para que todas as requisies da aplicao passem por esse filtro.
Nota: Numa aplicao web que faz uso do Cayenne, uma mesma instncia de DataContext compartilhada
por toda a sesso do usurio. Naturalmente, no caso de existirem diferentes sesses, estas faro uso de
diferentes DataContext e, portanto, de conjuntos diferentes de objetos.
Uma vez finalizadas as configuraes iniciais no web.xml, pode-se dar incio construo da interface web
propriamente dita. Embora todo o cdigo da view esteja centralizado em uma nica pgina XHTML
crud.xhtml importante observar que esta referencia outros quatro arquivos XHTML, os quais constituem
arquivos de composio da pgina. A Listagem 4 apresenta o cdigo do arquivo crud.xhtml.
Listagem 4. Cdigo da pgina crud.xhtml.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:ui="http://java.sun.com/jsf/facelets">
<h:head>
<title>Amanaj</title>
<h:outputStylesheet name="css/crud.css"/>
<h:outputScript name="js/crud.js"/>
</h:head>
<h:body>
<h:outputText value="Gerenciamento de Usurios" class="header"/>
<br/> <br/>
<fieldset>
<legend>Painel de Controle</legend>
<h:panelGrid columns="2">
<h:commandButton value="Criar Usurio" onclick="showdiv('div_cadastro')"/>
<h:commandButton value="Listar Usurios" onclick="showdiv('div_listagem')"/>

21

<h:commandButton
value="Atualizar
Usurio"
onclick="showdiv('div_atualizacao')"/>
<h:commandButton value="Remover Usurio" onclick="showdiv('div_remocao')"/>
</h:panelGrid>
</fieldset>
<br/> <br/>
<fieldset>
<legend>Painel de Operaes</legend>
<ui:include src="criar.xhtml"/>
<ui:include src="atualizar.xhtml"/>
<ui:include src="listar.xhtml"/>
<ui:include src="remover.xhtml"/>
</fieldset>
<h:messages/>
<div id="msg_sucesso">
<h:outputText
value="Operao
Efetuada
rendered="#{usuarioBean.exibirMensagemSucesso}"/>
<h:outputText
value="Nenhum
usurio
rendered="#{usuarioBean.exibirMensagemFalhaBusca}"/>
</div>

com
foi

Sucesso!"
encontrado!"

</h:body>
</html>

Note que, neste arquivo da aplicao, foram implementadas quatro operaes bsicas para a manipulao
dos dados:
1. Criar usurio com suas respectivas qualificaes (arquivo criar.xhtml);
2. Listar os usurios e qualificaes cadastrados no sistema (arquivo listar.xhtml);
3. Atualizar um usurio (arquivo atualizar.xhtml);
4. Remover um usurio e suas qualificaes do sistema (arquivo remover.xhtml).
Essas operaes podem ser acionadas pelo Painel de Controle da aplicao. Neste painel, cada boto
disponibiliza um formulrio no Painel de Operaes para execuo da respectiva operao. A visibilidade
de cada formulrio controlada por cdigo JavaScript atravs do mtodo showdiv, o qual disparado a
partir do evento onClick associado a cada boto do Painel de Controle.
Outro detalhe da pgina da aplicao o uso de um nico Managed Bean referenciado como usuarioBean
no qual todo o cdigo de controle da view est implementado, como por exemplo, a deciso de exibio
das mensagens de sucesso ou falha aps a execuo das operaes.
A fim de manter o foco na utilizao do Cayenne, ser apresentado o cdigo completo de apenas um arquivo
de composio da pgina criar.xhtml responsvel pela captao dos dados para insero de um novo
usurio.
Naturalmente, os demais arquivos (listar.xhtml, atualizar.xhtml e remover.xhtml) que compem a pgina
crud.xhtml contm cdigo XHTML anlogo ao que foi implementado para a funcionalidade de criao de
usurio.

22

A Listagem 5 apresenta o cdigo do arquivo criar.xhtml e a Listagem 6 exibe o cdigo do Managed Bean
associado pgina crud.xhtml.
Listagem 5. Cdigo do arquivo criar.xhtml.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:ui="http://java.sun.com/jsf/facelets">
<ui:composition>
<div id="div_cadastro" class="div_operacao">
<div id="titulo_operacao">
<h:outputText value="Cadastro de Usurio"/>
</div>
<h:form>
<h:panelGrid columns="2">
<h:outputLabel value="Documento" for="doc_usuario" />
<h:inputText value="#{usuarioBean.usuario.identidade}"
id="doc_usuario" maxlength="30" size="30" required="true"
requiredMessage="Documento de identificao do usurio obrigatrio."/>
<h:outputLabel value="Nome do Usurio" for="nome_usuario" />
<h:inputText value="#{usuarioBean.usuario.nome}" id="nome_usuario"
maxlength="60" size="60" required="true"
requiredMessage="Nome do usurio obrigatrio."/>
<h:outputLabel value="Data de Nascimento (dd/mm/aaaa)"
for="dtNascimento_usuario" />
<h:inputText value="#{usuarioBean.usuario.dataNascimento}"
id="dtNascimento_usuario" maxlength="10" size="10" required="true"
requiredMessage="Data de nascimento obrigatria.">
<f:convertDateTime pattern="dd/MM/yyyy"/>
</h:inputText>
<h:outputLabel value="Sexo" for="sexo_usuario"/>
<h:selectOneRadio value="#{usuarioBean.usuario.sexo}" id="sexo_usuario"
required="true" requiredMessage="Campo sexo obrigatrio.">
<f:selectItem itemLabel="Masculino" itemValue="M"/>
<f:selectItem itemLabel="Feminino" itemValue="F"/>
</h:selectOneRadio>
<h:outputLabel value="Qualificao" for="curso_usuario"/>
<h:selectManyCheckbox value="${usuarioBean.nivelQualificacaoes}"
id="curso_usuario">
<f:selectItems value="#{usuarioBean.tiposQualificacoes}"/>
</h:selectManyCheckbox>
<h:commandButton action="#{usuarioBean.cadastrar}" value="Cadastar Usurio" />
</h:panelGrid>
</h:form>
</div>
</ui:composition>
</html>

Listagem 6. Cdigo do managed bean UsuarioBean.


@ManagedBean
@RequestScoped
public class UsuarioBean {
private boolean exibirMensagemSucesso;
private boolean exibirMensagemFalhaBusca;
private Usuario usuario;
private List<Usuario> usuariosCadastrados;

23

private SelectItem[] tiposQualificacoes;


private int[] nivelQualificacaoes;
private UsuarioDAO usuarioDao;
private TipoQualificacaoDAO tipoDao;
public UsuarioBean() {
this.usuarioDao = new UsuarioDAO();
this.tipoDao = new TipoQualificacaoDAO();
loadTiposQualificacoes();
limparContexto();
}
public void limparContexto() {
this.exibirMensagemSucesso =
this.exibirMensagemFalhaBusca = false;
this.usuario = new Usuario();
this.usuariosCadastrados = new ArrayList<Usuario>();
this.nivelQualificacaoes = null;
}
public void cadastrar() {
this.usuarioDao.criar(usuario, nivelQualificacaoes);
limparContexto();
this.exibirMensagemSucesso = true;
}
public void recuperar() {
if (this.usuario.temIdentidade()) {
this.usuario = this.usuarioDao.recuperar(usuario.getIdentidade());
if (this.usuario == null) {
this.exibirMensagemFalhaBusca = true;
return;
}
gerarNiveisQualificacoesUsuario();
}
else {
this.usuariosCadastrados = this.usuarioDao.recuperarLista();
}
}
public void atualizar() {
if (this.usuarioDao.atualizar(usuario, nivelQualificacaoes)) {
limparContexto();
this.exibirMensagemSucesso = true;
}
else {
this.exibirMensagemFalhaBusca = true;
}
}
public void remover() {
if (this.usuarioDao.remover(usuario)) {
this.exibirMensagemSucesso = true;
limparContexto();
}
else {
this.exibirMensagemFalhaBusca = true;
}
}
private void carregarTiposQualificacoes() {
List<SelectItem> listaItens = new ArrayList<SelectItem>();
for (TipoQualificacao tipo : this.tipoDao.cadastrarListaPadrao())
listaItens.add(new SelectItem(tipo.getNivel(), tipo.getCurso()));

24

this.tiposQualificacoes = listaItens.toArray(new SelectItem[listaItens.size()]);


}
private void gerarNiveisQualificacoesUsuario() {
if (this.usuario.getQualificacoes() == null ||
this.usuario.getQualificacoes().isEmpty()) return;
int quantQualificacoes = this.usuario.getQualificacoes().size();
this.nivelQualificacaoes = new int[quantQualificacoes];
for (int numCurso = 0; numCurso < quantQualificacoes;
this.nivelQualificacaoes[numCurso] =
this.usuario.getQualificacoes().get(numCurso++).getNivel());
}
/**
* Getters e Setters
*/
}

Observe que ao UsuarioBean esto agregados dois objetos que disponibilizam o acesso aos dados do
sistema UsuarioDAO e TipoQualificacaoDAO.
Todo o cdigo de interao com o Cayenne est centralizado nessas duas classes. Ainda no managed bean,
foram implementados essencialmente quatro mtodos: cadastrar(), recuperar(), atualizar() e remover(),
os quais executam as funcionalidades oferecidas pelo sistema.
Alm destes, dois mtodos adicionais e privados foram construdos para dar suporte aos mtodos principais:
carregarTiposQualificacoes(): Registra na base de dados e recupera dela os tipos de qualificaes
disponveis para serem associados aos usurios do sistema. Os tipos disponveis so armazenados num
atributo do bean (tiposQualificacoes) que est associado a um componente da tela;
gerarNiveisQualificacoesUsuario(): Extrai os cdigos dos tipos de qualificaes recuperados de um
usurio, e os armazena em um atributo do bean (nivelQualificacoes). Isso possibilita que, por ocasio de
uma consulta a um usurio, seus tipos de qualificao associados possam ser utilizados por um componente
de tela na renderizao da pgina.
As Listagens 7 e 8 apresentam o cdigo das classes de persistncia responsveis pela interao com o
Cayenne. J a Listagem 9 apresenta o cdigo de um Enum auxiliar, usado no cadastro dos tipos de
qualificaes disponibilizados pelo sistema.
Listagem 7. Cdigo da classe UsuarioDAO.
public class UsuarioDAO {
private ObjectContext context;
private TipoQualificacaoDAO tipoDao;
public UsuarioDAO() {
this.context = BaseContext.getThreadObjectContext();
this.tipoDao = new TipoQualificacaoDAO(context);
}
public void criar(Usuario usuario, int[] niveisQualificacoes) {
this.context.registerNewObject(usuario);
for (int nivel : niveisQualificacoes) {
TipoQualificacao tipo = this.tipoDao.recuperarPorNivel(nivel);
usuario.addToQualificacoes(tipo);
}
this.context.commitChanges();
}

25

public boolean atualizar(Usuario usuario, int[] niveisQualificacoes) {


Usuario usuarioCommited = recuperar(usuario.getIdentidade());
if (usuarioCommited == null) return false;
List<TipoQualificacao> listaCursos = new ArrayList<TipoQualificacao>();
listaCursos.addAll(usuarioCommited.getQualificacoes());
for (TipoQualificacao curso : listaCursos)
usuarioCommited.removeFromQualificacoes(curso);
for (int nivel : niveisQualificacoes)
usuarioCommited.addToQualificacoes(this.tipoDao.recuperarPorNivel(nivel));
usuarioCommited.merge(usuario);
this.context.commitChanges();
return true;
}
public boolean remover(Usuario usuario) {
Usuario usuarioCommited = recuperar(usuario.getIdentidade());
if (usuarioCommited == null) return false;
List<TipoQualificacao> listaCursos = new ArrayList<TipoQualificacao>();
listaCursos.addAll(usuarioCommited.getQualificacoes());
for (TipoQualificacao curso : listaCursos)
usuarioCommited.removeFromQualificacoes(curso);
this.context.deleteObject(usuarioCommited);
this.context.commitChanges();
return true;
}
@SuppressWarnings("unchecked")
public Usuario recuperar(String identidade) {
SelectQuery query = new SelectQuery(Usuario.class,
ExpressionFactory.matchExp(Usuario.IDENTIDADE_PROPERTY, identidade));
List<Usuario> listaRecuperada = this.context.performQuery(query);
return listaRecuperada != null && (! listaRecuperada.isEmpty()) ?
(Usuario) listaRecuperada.get(0) : null;
}
@SuppressWarnings("unchecked")
public List<Usuario> recuperarLista() {
SelectQuery query = new SelectQuery(Usuario.class);
query.addOrdering(Usuario.NOME_PROPERTY, SortOrder.ASCENDING);
return this.context.performQuery(query);
}
}

Listagem 8. Cdigo da classe TipoQualificacaoDAO.


public class TipoQualificacaoDAO {
private ObjectContext context;
public TipoQualificacaoDAO() {
this.context = BaseContext.getThreadObjectContext();
}
public TipoQualificacaoDAO(ObjectContext context) {
this.context = context;
}

26

public List<TipoQualificacao> cadastrarListaPadrao() {


List<TipoQualificacao> listaTipos = recuperarLista();
if (listaTipos.isEmpty()) {
for (Qualificacao qualificacao : Qualificacao.values()) {
TipoQualificacao tipo = new TipoQualificacao();
tipo.setCurso(qualificacao.getCurso());
tipo.setNivel(qualificacao.getNivel());
tipo.setSigla(qualificacao.getSigla());
this.context.registerNewObject(tipo);
}
this.context.commitChanges();
}
return listaTipos;
}
@SuppressWarnings("unchecked")
public List<TipoQualificacao> recuperarLista() {
SelectQuery query = new SelectQuery(TipoQualificacao.class);
query.addOrdering(TipoQualificacao.NIVEL_PROPERTY, SortOrder.ASCENDING);
return this.context.performQuery(query);
}
@SuppressWarnings("unchecked")
public TipoQualificacao recuperarPorNivel(int nivel) {
SelectQuery query = new SelectQuery(TipoQualificacao.class,
ExpressionFactory.matchExp(TipoQualificacao.NIVEL_PROPERTY, nivel));
List<TipoQualificacao> listaRecuperada = this.context.performQuery(query);
return listaRecuperada != null && (! listaRecuperada.isEmpty()) ?
(TipoQualificacao) listaRecuperada.get(0) : null;
}
}

Listagem 9. Cdigo do enum Qualificacao.


public enum Qualificacao {
ENSINO_MEDIO("Ensino Mdio", 10, "ENM"),
GRADUACAO("Graduao", 20, "GRD"),
ESPECIALIZACAO("Especializao", 30, "ESP"),
MESTRADO("Mestrado", 40, "MST"),
DOUTORADO("Doutorado", 50, "DTD");
private String curso;
private int nivel;
private String sigla;
private Qualificacao(String curso, int nivel, String sigla) {
this.curso = curso;
this.nivel = nivel;
this.sigla = sigla;
}
public String getCurso() {
return curso;
}
public int getNivel() {
return nivel;
}
public String getSigla() {
return sigla;
}

27

Ao analisar o cdigo de persistncia, importante notar que os mtodos construtores de ambas as classes
buscam a referncia ao ObjectContext para poderem interagir com o Cayenne. Essa referncia recuperada
a partir de uma chamada ao mtodo esttico getThreadObjectContext() da classe abstrata BaseContext. E,
no caso de TipoQualificacaoDAO, ela tambm pode ser passada como parmetro para o construtor da
classe.
Avanando ao detalhamento de cada DAO, observe que a classe UsuarioDAO implementa cinco mtodos
para manipulao dos dados via Cayenne. So eles:
criar(): Para insero de um novo usurio no sistema, primeiramente necessrio registrar o objeto,
passado como parmetro, no contexto de persistncia (ObjectContext).
Isso feito via chamada ao mtodo registerNewObject(), o qual faz com que a instncia deixe o estado
transient e passe ao estado new. Como o mtodo criar() no recebe a lista de TipoQualificacao do usurio,
mas sim um array com os cdigos das respectivas qualificaes, ento preciso recuperar essa lista e
agreg-la nova instncia de Usuario.
Uma vez executadas todas essas operaes, o novo usurio est pronto para ser persistido como registro no
banco de dados. Para isso, deve-se executar uma chamada ao mtodo commitChanges() do contexto de
persistncia. Isso faz com que o objeto deixe o estado new e passe para o estado commited;
atualizar(): Executa a atualizao de usurios no sistema. Note que o mtodo recebe como parmetro um
usurio com seus atributos atualizados, e um array contendo as novas qualificaes que devem substituir as
atualmente persistidas. Logo, primeiramente preciso buscar a instncia de Usuario que est no banco,
juntamente com suas respectivas qualificaes.
De posse dessa instncia, todas as correspondentes qualificaes recuperadas so removidas e,
subsequentemente, substitudas pelas pertencentes ao array informado.
Alm disso, a instncia recuperada do banco tem seus atributos sobrescritos pela instncia passada como
parmetro. Esta operao acarreta ainda que a instncia de Usuario, que foi buscada da base, deixe o estado
commited e passe para o estado modified. Porm, at este ponto, nenhuma modificao efetuada nos objetos
foi refletida nos respectivos registros do banco de dados. Para que isso seja executado, novamente o mtodo
commitChanges() do contexto de persistncia invocado, e ento o objeto retorna para o estado commited;
remover(): A remoo do objeto Usuario e suas respectivas qualificaes se d de forma anloga
atualizao, salvo pelo fato de que as qualificaes agregadas instncia informada sero apenas removidas
e no substitudas.
A invocao do mtodo deleteObject() faz com que o objeto saia do estado commited e passe para o estado
deleted. Aps o commit das modificaes, via mtodo commitChanges(), o respectivo registro do banco de
dados removido;
recuperar() e recuperarLista(): Em ambos os mtodos, a recuperao dos objetos feita por meio da
classe SelectQuery. Para informar os parmetros da consulta, utilizada a chamada
ExpressioinFactoy.matchExp(), a qual abstrai a clusula where do cdigo SQL. No caso do mtodo
recuperar(), um parmetro referente identidade do usurio deve ser informado. Uma vez construda a
consulta, esta executada via chamada performQuery() do contexto de persistncia.

28

A segunda classe de persistncia construda a classe TipoQualificacaoDAO implementa apenas dois


mtodos principais recuperarLista() e recuperarporNivel() e um mtodo de apoio
cadastrarListaPadrao().
Este ltimo usado apenas para cadastrar os tipos de qualificao do sistema na base de dados. Por sua vez,
a implementao dos dois primeiros mtodos anloga ao que foi construdo na classe UsuarioDAO.
Finalizada a construo da camada de persistncia, a aplicao Amanaje encontra-se pronta para deploy. A
sua instalao no container JBoss AS gera um log cuja anlise permite verificar a sequncia de criao e
instanciao dos diversos recursos por ela utilizados. Um trecho desse log apresentado na Listagem 10.
Listagem 10. Log de inicializao da aplicao Amanaje no JBoss AS.
[org.jboss.web.tomcat.service.deployers.TomcatDeployment] deploy, ctxPath=/amanaje
[javax.enterprise.resource.webcontainer.jsf.config] Inicializando Mojarra 2.0.3 (
b05) para o contexto '/amanaje'
[javax.enterprise.resource.webcontainer.jsf.config]
Monitoring
jndi:/localhost/amanaje/WEB-INF/faces-config.xml for modifications
[org.apache.cayenne.conf.RuntimeLoadDelegate] started configuration loading.
[org.apache.cayenne.conf.RuntimeLoadDelegate] loaded domain: AmanajeDomain
[org.apache.cayenne.conf.RuntimeLoadDelegate] loaded <map name='AmanajeDomainMap'
location='AmanajeDomainMap.map.xml'>.
[org.apache.cayenne.conf.RuntimeLoadDelegate] loading <node name='AmanajeDomainNode'
datasource='AmanajeDomainNode.driver.xml'
factory='org.apache.cayenne.conf.DriverDataSourceFactory'
schema-updatestrategy='org.apache.cayenne.access.dbsync.CreateIfNoSchemaStrategy'>.
[org.apache.cayenne.conf.RuntimeLoadDelegate]
using
factory:
org.apache.cayenne.conf.DriverDataSourceFactory
[org.apache.cayenne.conf.DriverDataSourceFactory] loading driver information from
'AmanajeDomainNode.driver.xml'.
[org.apache.cayenne.conf.DriverDataSourceFactory]
loading
driver
org.postgresql.Driver
[org.apache.cayenne.conf.DriverDataSourceFactory] loading user name and password.
[org.apache.cayenne.access.QueryLogger]
Created
connection
pool:
jdbc:postgresql://localhost:5432/postgres
Driver class: org.postgresql.Driver
Min. connections in the pool: 1
Max. connections in the pool: 3
[org.apache.cayenne.conf.RuntimeLoadDelegate] loaded datasource.
[org.apache.cayenne.conf.RuntimeLoadDelegate]
no
adapter
set,
using
automatic
adapter.
[org.apache.cayenne.conf.RuntimeLoadDelegate] loaded map-ref: AmanajeDomainMap.
[org.apache.cayenne.conf.RuntimeLoadDelegate] finished configuration loading in 132
ms.

Pode-se observar que, por ocasio do carregamento do contexto da aplicao (/amanaje) no container,
vrios recursos so acionados:
Carregamento da implementao JSF do JBoss (Mojarra);
Carregamento das configuraes do projeto Cayenne (AmanajeDomain) presentes no arquivo cayenne.xml;
Carregamento dos mapeamentos objeto-relacionais construdos (AmanajeDomainMap) e indicados no
arquivo AmanajeDominMap.map.xml;
Carregamento das configuraes de acesso ao banco de dados AmanajeDomainNode.driver.xml;
Conexo com o banco de dados e gerao do pool de conexes.
29

A Figura 12 apresenta uma viso da interface principal da aplicao Amanaje juntamente com o formulrio
de cadastro de usurio preenchido. J a Listagem 11 apresenta um trecho do log gerado aps o acionamento
do boto Cadastrar Usurio da aplicao.

Figura 12. Tela de cadastro da aplicao Amanaje.


Listagem 11. Log gerado para o cadastramento de um usurio na aplicao.
[org.apache.cayenne.access.QueryLogger] --- will run 1 query.
[org.apache.cayenne.access.QueryLogger] --- transaction started.
[org.apache.cayenne.access.QueryLogger] SELECT t0.sigla, t0.curso, t0.id, t0.nivel
FROM amanaje.tipo_qualificacao t0 WHERE t0.nivel = ? [bind: 1->nivel:30]
[org.apache.cayenne.access.QueryLogger] === returned 1 row. - took 1 ms.
[org.apache.cayenne.access.QueryLogger] +++ transaction committed.
[org.apache.cayenne.access.QueryLogger] --- transaction started.
[org.apache.cayenne.access.QueryLogger] SELECT nextval('amanaje.pk_usuario')
[org.apache.cayenne.access.QueryLogger] --- will run 2 queries.
[org.apache.cayenne.access.QueryLogger] INSERT INTO amanaje.usuario (data_nascimento,
id, identidade, nome, sexo) VALUES (?, ?, ?, ?, ?)
[org.apache.cayenne.access.QueryLogger] [batch bind: 1->data_nascimento:'2014-04-13
21:00:00.0', 2->id:280, 3->identidade:'123456',
4->nome:'Java Magazine DevMedia', 5->sexo:'M']
[org.apache.cayenne.access.QueryLogger] === updated 1 row.
[org.apache.cayenne.access.QueryLogger] INSERT INTO amanaje.qualificacao (id_tipo,
id_usuario) VALUES (?, ?)
[org.apache.cayenne.access.QueryLogger]
[batch
bind:
1->id_tipo:221,
2>id_usuario:280]
[org.apache.cayenne.access.QueryLogger] === updated 1 row.
[org.apache.cayenne.access.QueryLogger] +++ transaction committed.

Note que o log indica a execuo de duas transaes, ambas delimitadas pelas informaes de transaction
started e transaction commited:
1. Na primeira transao, so recuperados do banco todos os tipos de qualificao a serem disponibilizados
num atributo do bean e associados a um componente da tela;

30

2. Na segunda transao, ocorre a manipulao dos dados do banco envolvidos na efetivao do cadastro do
usurio, a saber:
a) Um novo valor gerado para representar a primary key do registro a ser inserido na tabela USUARIO;
b) Um novo registro, contendo os dados informados na tela, inserido na tabela USUARIO;
c) Um novo registro criado na join table QUALIFICACAO para indicar os tipos de qualificaes
cadastrados para o usurio informado.
Neste ponto, ressalta-se novamente que diversos aspectos considerados como boas prticas para a
construo de aplicaes web foram abstrados.
Esse caso, por exemplo, da internacionalizao de Strings e validao de dados. Contudo, isso possibilitou
a manuteno do foco na utilizao do Cayenne e na apresentao dos seus recursos.
A utilizao do Cayenne constitui uma alternativa simples e eficiente para aplicaes cuja persistncia
baseada em mapeamentos objeto-relacionais.
Assim como a vasta gama de frameworks que implementam a especificao JPA, ele oferece um mecanismo
de ORM que contempla requisitos para solues nos mais diferentes contextos. Contudo, atravs de sua
integrao com uma poderosa ferramenta grfica o CayenneModeler, ele agrega o diferencial de abstrair
do desenvolvedor qualquer configurao via anotaes ou arquivos XML.
Isso confere ao Cayenne o carter de uma ferramenta gil e adequada para a implementao de projetos que
faam uso de modelos orientados a objetos persistidos em bancos de dados relacionais.
Links
Site oficial do projeto Apache Cayenne.
https://cayenne.apache.org/
Artigo que apresenta o passo-a-passo de criao de um projeto com o Apache Cayenne.
http://www.theserverside.com/news/1364760/Cayenne-BeingProductive-with-Object-Relational-Mapping
Frum de discusso sobre o Apache Cayenne.
http://cayenne.195.n3.nabble.com/[/nota]

31

Você também pode gostar