FACULDADE FARIAS BRITO
CIÊNCIA DA COMPUTAÇÃO
Thiago Vicente Benega
Padrões de Projeto em Modelagem Orientada a Objetos Persistida em
Banco de Dados Relacional
Fortaleza 2010
Thiago Vicente Benega
Padrões de Projeto em Modelagem Orientada a Objetos Persistida
em Banco de Dados Relacional
Monografia apresentada para obtenção dos
créditos da disciplina Trabalho de Conclusão do
Curso da Faculdade Farias Brito, como parte das
exigências para graduação no Curso de Ciência da
Computação.
Orientador: MSc. Ricardo Wagner C. Brito
Fortaleza 2010
PADRÕES DE PROJETO EM MODELAGEM ORIENTADA A
OBJETOS PERSISTIDA EM BANCO DE DADOS
RELACIONAL
Thiago Vicente Benega
PARECER __________________
NOTA:
FINAL (0 – 10): _______
Data: ____/____/_________
BANCA EXAMINADORA:
___________________________________
Nome e titulação
(Orientador)
___________________________________
Nome e titulação
(Examinador)
__________________________________
Nome e titulação
(Examinador)
RESUMO
O uso do paradigma Orientado a Objetos se tornou o mais utilizado na área de
desenvolvimento de sistemas devido a sua maior aproximação com os elementos do mundo
real e sua facilidade de reutilização de código. No entanto, o mecanismo de armazenamento e
recuperação de dados mais comumente utilizado - o Banco de Dados Relacional - não foi
projetado para interagir diretamente com o paradigma OO e com suas particularidades.
Desenvolver aplicações comerciais utilizando estes dois modelos com características tão
diferentes é um problema conhecido na computação e novas soluções para este problema
continuam sendo pesquisadas e desenvolvidas. Este trabalho se propõe a identificar um
conjunto de problemas conhecidos na utilização destes dois modelos no desenvolvimento de
software e propor soluções que venham a minimizar tais problemas.
SUMÁRIO
INTRODUÇÃO .......................................................................................................................... 9
1
2
Conceitos Básicos.............................................................................................................. 12
1.1
Persistências de Dados............................................................................................... 12
1.2
Modelo Orientado a Objeto ...................................................................................... 14
1.3
OO X RELACIONAL ............................................................................................... 16
1.4
Padrões de projeto ..................................................................................................... 21
1.5
Ferramentas ORM..................................................................................................... 23
1.5.1
Hibernate............................................................................................................. 23
1.5.2
Ibatis .................................................................................................................... 24
1.5.3
CocoBase ............................................................................................................. 25
Problemas Encontrados ................................................................................................... 26
2.1
2.1.1
Cargas Duplicadas.............................................................................................. 27
2.1.2
Carga Desnecessária de Dados .......................................................................... 28
2.1.3
Tráfego de Carga Com Dados Redundantes ................................................... 29
2.1.4
Consultas Polimórficas Ou Hierárquicas......................................................... 30
2.2
Mapeamento ............................................................................................................... 31
2.2.1
Tabelas Com Colunas de Nomes Iguais ........................................................... 31
2.2.2
Campos de Retorno Dinâmicos ......................................................................... 32
2.2.3
Acoplamento Com o Banco de Dados............................................................... 32
2.3
3
Carga de Dados .......................................................................................................... 27
Descarga de Dados ..................................................................................................... 33
2.3.1
Descarregar Apenas o Necessário ..................................................................... 33
2.3.2
Descarregar Apenas Quando Necessário ......................................................... 34
REPOO (Repositório Orientado a Objetos) .................................................................. 35
3.1
Classes ......................................................................................................................... 38
4
3.2
Arquivos...................................................................................................................... 40
3.3
Funcionamento........................................................................................................... 40
3.3.1
Consulta e Preenchimento ................................................................................. 41
3.3.2
Persistência.......................................................................................................... 44
3.3.3
Exclusão............................................................................................................... 44
Soluções Propostas ........................................................................................................... 45
4.1
4.1.1
Cargas Duplicadas.............................................................................................. 45
4.1.2
Carga Desnecessária de Dados .......................................................................... 46
4.1.3
Tráfego de Cargas Redundantes....................................................................... 48
4.1.4
Consultas Polimórficas Ou Hierárquicas......................................................... 49
4.2
Mapeamento ............................................................................................................... 49
4.2.1
Tabelas com Colunas de Nomes Iguais............................................................. 50
4.2.2
Campos de retorno dinâmicos........................................................................... 50
4.2.3
Acoplamento com o Banco de Dados ................................................................ 51
4.3
5
Cargas ......................................................................................................................... 45
Descarga de Dados ..................................................................................................... 52
4.3.1
Descarregar Apenas o Necessário ..................................................................... 52
4.3.2
Descarregar Apenas Quando Necessário ......................................................... 53
Conclusão .......................................................................................................................... 54
5.1
Análise comparativa .................................................................................................. 54
5.2
Contribuições ............................................................................................................. 55
6
Trabalhos Futuros ............................................................................................................ 57
7
Referências Bibliográficas ............................................................................................... 58
8
Material de Leitura .......................................................................................................... 61
LISTA DE FIGURAS
Figura 1. Duas hierarquias de objetos distintas......................................................................... 18
Figura 2. Mapeamento em uma única tabela............................................................................. 18
Figura 3. Mapeamento em classes concretas............................................................................. 19
Figura 4. Mapeamento para cada classe.................................................................................... 19
Figura 5. Mapeamento para cada classe.................................................................................... 20
Figura 6. Modelo de dados Aluno e Disciplinas ....................................................................... 27
Figura 7. Camada REPOO ........................................................................................................ 36
Figura 8. Propriedades, Campos e Colunas............................................................................... 38
Figura 9. Modelo Aluno alterado .............................................................................................. 52
LISTA DE TABELAS
Tabela 1: Um select retornando os dados de um aluno....................................................26
INTRODUÇÃO
Persistência de dados consiste em manter certas informações em um estado não-volátil
de forma que, ao desligar o computador, tais dados não sejam descartados. As primeiras
versões do conceito de persistência de dados eram simples, baseadas em arquivos binários. A
aplicação se encarregava de manter sua integridade e concorrência.
Com o surgimento dos Sistemas Gerenciadores de Banco de Dados (SGBDs), o
gerenciamento da persistência de dados foi desacoplado da aplicação e centralizado no SGBD.
Dentre os vários tipos de modelos de dados utilizados ao longo dos últimos anos, o Modelo
Relacional se consolidou como o mais popular, sendo amplamente utilizado na quase
totalidade dos principais SGBDs atualmente disponíveis no mercado.
O Modelo Relacional possui como elementos básicos as tabelas, as quais, por sua vez,
são compostas de linhas (ou tuplas) e colunas (ou atributos). Em cada linha são armazenados
valores relacionados a um determinado elemento (um aluno, uma disciplina ou um produto, por
exemplo). As colunas agrupam valores homogêneos referentes a um determinado atributo dos
elementos (nome, código, preço, etc).
O desenvolvimento de sistemas também sofreu diversas alterações durante a história.
Os primeiros paradigmas e linguagens definiam a forma de desenvolvimento de modo
procedimental. O pensamento era similar ao de como o hardware funciona, de forma linear, e
tinha uma aceitação razoável com o modelo de persistência relacional. Porém, esse modelo de
desenvolvimento era de difícil manutenção e legibilidade. Em sequência a esse paradigma de
desenvolvimento, surgiu o paradigma da Programação Orientada a Objetos (POO).
Neste paradigma, propõe-se um distanciamento do hardware e uma aproximação do
mundo real. Tudo é objeto, assim como no nosso mundo, e estes interagem através do envio de
mensagens,
tendo
cada
objeto
suas
próprias
características,
comportamento
e
responsabilidades. Esse modelo de analisar e desenvolver sistemas facilitou muito a
vida dos desenvolvedores.
Por sua vez, o modelo de persistência também vem sofrendo alterações, com o
surgimento de outros paradigmas como é o caso dos SGBDs Orientados a Objetos (SGBDOO).
Este modelo de banco de dados foi desenvolvido com o objetivo de tentar atender as
características do paradigma OO que não são tratadas pelo modelo relacional. No entanto,
diversos problemas foram encontrados ao se tentar substituir o modelo relacional por essa nova
abordagem. Tais problemas serão analisados mais adiante neste trabalho.
Atualmente, a solução mais amplamente utilizada é o paradigma OO de
desenvolvimento persistindo em um paradigma relacional de armazenamento de dados. O
problema dessa solução é que ela apresenta dois paradigmas diferentes com a dificuldade em
compatibilizá-los. Na busca por soluções, surgem várias possibilidades e, com elas, um
conjunto de ideias, tais como frameworks e bibliotecas que possuem vantagens e desvantagens.
Algumas optam por desempenho e outras por flexibilidade.
Este trabalho se propõe a apresentar um conjunto de soluções e práticas para o processo
de intermediação entre os dois paradigmas citados, utilizando Padrões de Projetos e com base
nas análises das ferramentas mais populares existentes no mercado (Hibernate, CocoBase,
Ibatis, entre outras). Esse conjunto de soluções pretende atender a alguns casos específicos, que
serão catalogados de acordo com análises realizadas. Para isso, tais ferramentas serão
analisadas com base em certos critérios pré-definidos, os quais serão identificados mais
adiante.
A análise e o desenvolvimento das soluções terão como ênfase os aspectos de
produtividade, simplicidade, e flexibilidade, permitindo o uso das soluções visando
automatizar parte do esforço de desenvolvimento de uma forma simples, e possibilitando
também o não acoplamento da aplicação com o banco de dados.
Apesar da solução esperada - fornecer saídas para as duas etapas do processo de
persistência de dados, carga ou consulta e armazenamento - o enfoque maior estará na etapa de
consulta pela sua maior complexidade e importância. As consultas escritas em linguagem SQL
10
podem ser tão complexas quanto à necessidade do usuário e a maior incompatibilidade entre o
modelo OO e o modelo relacional está na forma como essa consulta é retornada.
11
1 Conceitos Básicos
Neste capítulo serão abordados alguns conceitos que envolvem a persistência de dados
no ambiente de desenvolvimento de software, seguida pela explicação das dificuldades e das
soluções atualmente utilizadas.
1.1 Persistências de Dados
Memória em arquitetura de computadores é o módulo existente para o armazenamento
dos dados. Sem este recurso, não seria possível executar nenhum programa com as arquiteturas
atualmente utilizadas. Existem dois tipos de memórias no que se refere à persistência de dados:
as voláteis, nas quais, ao se desligar o computador, todos os dados são perdidos e as nãovoláteis que mantêm seus dados após o reinício do computador ou do sistema (STALLING,
2002).
Os sistemas desenvolvidos devem, de alguma maneira, gerenciar a persistência dos
dados. A primeira forma de gerência utilizava simples arquivos binários e todo o controle
ficava por conta do próprio sistema. Diversos programas são criados para controlar a
armazenagem e a extração dos dados e, conforme novas regras e módulos são adicionados ao
sistema, novos programas de controles precisam ser criados. Existia um forte acoplamento
entre a aplicação e o gerenciamento de persistência. Era essa a realidade dos sistemas antes do
advento dos Sistemas Gerenciadores de Banco de Dados (SGBDs). (SILBERSCHATZ,
KORTH, SUDARSHAN, 2006).
Com o surgimento dos SGBDs, o esforço computacional para se manter os dados é
retirado da aplicação e todo o controle de persistência e regras é centralizado no SGBD. Em
12
Date (2004, p.16-18), são encontradas outras vantagens em se ter um sistema centralizado para
gerenciamento de dados, tais como:
- Os dados podem ser compartilhados. O acesso aos dados está desacoplado da
aplicação.
- A redundância pode ser minimizada. Em cenários sem a utilização de sistemas de
banco de dados, cada aplicação possui seus próprios arquivos, não importando se já existem os
arquivos desejados em outro local.
- A inconsistência pode ser evitada (até certo ponto). O SGBD possui regras para
manter a consistência dos dados, porém as relações e regras entre as tabelas devem ser
devidamente estabelecidas, caso contrário, o SGBD não tem como garantir a consistência.
- Suporte a transações. Transação em SGBD é uma unidade lógica de trabalho que
garante a atomicidade das operações. Caso alguma interferência ocorra no meio de uma
operação, o SGBD volta ao estado que estava ao iniciar a transação.
- Integridade pode ser mantida. Assegura que os dados no banco estão corretos. Por
estar centralizado, a manutenção da integridade se torna mais importante e mais fácil de ser
mantida do que em um cenário sem a utilização dos sistemas gerenciadores de banco de dados.
- Segurança. Existe o conceito de usuários dentro do SGBD, cada usuário possui níveis
de restrições. Assim, usuários que necessitem apenas de consultas podem ter restrições quanto
a efetuar alterações.
- Requisitos contraditórios podem ser equilibrados. O SGBD pode ser configurado de
forma a atender os requisitos que forem necessários para a empresa, atendendo a necessidades
conflitantes.
- Os padrões podem ser impostos. Como os dados estão centralizados, o
acompanhamento dos padrões impostos pela empresa se torna possível.
13
Os primeiros SGBDs a serem utilizados em aplicações comerciais seguiam o modelo
relacional e atendiam os critérios citados anteriormente (SILBERSCHATZ, KORTH,
SUDARSHAN, 2006).
Originalmente, os bancos de dados relacionais foram projetados para separar o
armazenamento físico dos dados da sua representação conceitual. Os SGBDs introduziram uma
linguagem de consulta de alto nível (SQL), facilitando e otimizando o acesso aos dados. Pela
questão de desempenho, de facilidade e prática manutenção, o uso de banco de dados
relacionais tornou-se comum na maioria dos computadores e sistemas até hoje (ELMASRI,
NAVATHE, 2006).
O banco de dados relacional armazena seus dados através de uma coleção de tabelas
que se relacionam entre si. Cada tabela contém um nome e um conjunto de linhas não
ordenadas, cada linha contém uma série de colunas, e cada coluna possui um nome, um tipo e
um valor. A identidade de uma linha é garantida pelo conceito de chave primária, e o
relacionamento entre as tabelas é possível pelo conceito de chave estrangeira.
Chave primária e chave estrangeira seguem o conceito de chave, que é um conjunto de
uma ou mais colunas que garantem identificação de uma linha perante as outras. A chave
primária é usada como a primeira chave a identificar a linha, outras chaves podem ser criadas
para garantir unicidade, estas levam o nome de chaves alternativas. Já a chave estrangeira
permite a criação de relacionamentos entre as tabelas. (HEUSER, 2004).
1.2 Modelo Orientado a Objeto
Em paralelo ao progresso de gerenciamento de dados, o desenvolvimento das
aplicações também passou por grandes mudanças. Um dos primeiros paradigmas de
desenvolvimento a se popularizar foi o procedimental ou estruturado. A maneira como se
codificava era linear, muito próximo ao hardware e utilizava conceitos matemáticos para
definir soluções. “Um algoritmo é uma sequência ordenada e finita de etapas, cuja execução
passo a passo resolve um determinado problema.” (VILARIM, 2004, p.7).
14
O desenvolvimento de aplicações baseadas neste paradigma era de uma complexidade
muito alta levando a descontinuação de vários projetos de software. Para tentar reverter essa
realidade, foi criada uma nova abordagem de desenvolvimento, o Paradigma Orientado a
Objetos. Este, por sua vez, permitia a criação de um código de melhor legibilidade, rotinas
podiam ser mais facilmente reutilizadas e processos complexos podiam ser escritos de forma
mais compreensível e de melhor manutenção.
Segundo Deitel (2006), orientação a objetos é um paradigma que aproxima o
programador do mundo real, no qual tudo pode ser visto como objetos, como por exemplo,
livro, aluno, faculdade, etc. Antes da OO, o desenvolvimento se preocupava com as ações
desses objetos, a programação se baseava nos verbos: alugarLivro, cadastrarAluno,
realizarMatricula, etc. O programador recebia os problemas em objetos e codificava em verbos.
Com o advento da OO, o programador passou a codificar exatamente o que via. A modelagem
passou a se basear nos substantivos, como, por exemplo, o livro, o aluno, etc..
Os objetos dentro da computação são unidades que encapsulam seu significado próprio,
possuem estados e comportamento que podem ser reutilizáveis. Os objetos, assim como no
mundo real, possuem relações e deveres com outros objetos.
Ao desenvolver um sistema OO, não se analisa o problema linearmente. Primeiro se
observa quais objetos estão interagindo entre si e qual a responsabilidade de cada um dentro do
contexto do problema. Essa nova forma de raciocinar tornou sistemas grandes e complexos
possíveis de serem realizados (DEITEL, 2006).
Para a realização de um código mais legível e reutilizável, a POO emprega alguns
conceitos em seu paradigma de desenvolvimento como: agregação, herança, polimorfismo,
associações, entre outros (DEITEL, 2006):
- Agregação: Estrutura todo-parte, conceito onde um objeto contém outros objetos
dentro de si. Existem dois tipos: agregação e agregação por composição, ou apenas
composição. A agregação consiste no relacionamento entre dois objetos, onde um pode viver
sem a existência do outro, diferentemente de composição, um relacionamento forte onde um
objeto necessita da existência do outro. Apesar dos dois conceitos serem diferentes
15
semanticamente, este trabalho não distinguirá os dois, visto que, quando se trabalha com
persistência de dados, os dois conceitos não se diferenciam (MEDEIROS, 2006).
- Herança: “A herança é uma forma de reutilização de software em que o programador
cria uma classe que absorve dados e comportamentos de uma classe existente e os aprimora
com novas capacidades” (DEITEL, 2006, p.502). Elementos mais específicos são
completamente consistentes com o mais geral, podem acessar seus atributos e métodos e
implementar novos (Medeiros, 2006). Com o recurso de herança podemos modelar uma
hierarquia de classes.
- Polimorfismo:
O polimorfismo permite ‘programar no geral’ em vez de ‘programar no
especifico’. Em particular, o polimorfismo permite escrever programas que
processam objetos de classes que fazem parte da mesma hierarquia de classes
como se todos fossem objetos da classe básica da hierarquia. (DEITEL, 2006,
p.546).
Com o polimorfismo pode-se trabalhar com qualquer representação do objeto. Por
exemplo, aluno e professor são objetos diferentes, porém ambos são pessoas, assim é possível
trabalhar com os dois tipos diferentes utilizando o tipo pessoa. Claro que com as limitações de
pessoa.
1.3 OO X RELACIONAL
O modelo relacional de persistência de dados não atende muito bem ao paradigma OO.
Os SGBDs não implementam nativamente o conceito de herança utilizado pelos objetos. Em
casos simples, os dois paradigmas se mostram menos conflitantes. Porém, objetos podem ser
tão complexos quanto necessário. Hierarquias extensas, muitas regras e relacionamentos
tornam difícil a representação em um modelo simples de tabelas e relacionamentos (BAUER,
KING, 2005).
16
Segundo Pinheiro (2005), os Bancos de Dados Orientados a Objetos não foram bem
aceitos pela maioria dos sistemas comerciais devido à predominância do banco de dados
relacional no mercado, com certa maturidade, estabilidade, com vários fornecedores e suporte,
o que tornou difícil a sua substituição.
Além disso, o tempo de respostas nas consultas do SGBDOO ainda são maiores do que
o dos SGBDs relacionais. A migração das bases de dados existentes de um banco relacional
para um orientado a objetos possui um alto custo, visto que se torna necessário analisar tabela
por tabela e respectivos relacionamentos, muitas vezes complexos, e transpor para um novo
banco com um novo paradigma. É necessário testar a aplicação inteira para garantir a
funcionalidade e aceitação do sistema para com o novo banco.
Com os SGBDs relacionais dominando o mercado, restavam duas alternativas:
desenvolver as tabelas e relações de um modo a representar de forma mais adequada os objetos
e seus conceitos, ou criar uma camada dentro da aplicação que mapeie os objetos com as
tabelas sem alterar a base de dados existente. A primeira alternativa é mais organizada, as
tabelas tentam representar da melhor forma o objeto e seus conceitos: hierarquia e
polimorfismo, agregação, associações entre classes (KELLER, 1997). A segunda alternativa é,
muitas vezes, mais interessante pela não necessidade de alterações no banco de dados. Em
projetos que se iniciam do zero, as duas técnicas podem ser utilizadas juntas, as tabelas criadas
de forma mais adequada ao POO e uma camada que faça o mapeamento dos dois paradigmas.
A modelagem das tabelas para representar OO tem de mapear os conceitos de OO
citados acima. Existem alguns padrões para tratar cada conceito (KELLER, 1997):
•
Hierarquia e polimorfismo.
Existem alguns padrões utilizados atualmente para representação da hierarquia de
objetos dentro das tabelas, tratando também o polimorfismo. Esses padrões e seus respectivos
exemplos podem ser visualizados na Figura 1 (AMBLER, on-line).
- Tabela única para toda estrutura hierárquica: todos os atributos de todas as classes da
estrutura hierárquica são mapeados em uma única tabela. Cada linha representa um objeto de
uma subclasse especifica. Uma coluna extra é criada na tabela para controlar a distinção entre
17
as classes. A Figura 1 representa um exemplo com duas hierarquias distintas, a Figura 2 ilustra
como é a representação das duas hierarquias utilizando essa técnica.
Figura 1. Duas hierarquias de objetos distintas
Figura 2. Mapeamento em uma única tabela
Todos os atributos das classes Pessoa, Cliente, Empregado e Executivo (no caso, à
direita), conforme a Figura 2, estão em uma única tabela onde existe um atributo (PessoaTipo)
que identifica o tipo do objeto, se é um Cliente ou um Empregado.
- Uma tabela para cada classe concreta: Para cada classe concreta existente na
aplicação, existe uma tabela correspondente no banco de dados. A tabela contém colunas
representando todos os atributos da classe inclusive os herdados pelas superclasses. A Figura 3
ilustra como fica o mapeamento utilizando essa técnica com base na Figura 1.
18
Figura 3. Mapeamento em classes concretas
Conforme a Figura 3, os atributos de Cliente, Empregado e, no segundo caso, ainda o
Executivo, possuem todos os atributos das superclasses.
- Uma tabela para cada classe: para cada classe da estrutura hierárquica, existe uma
tabela correspondente no banco de dados. A Figura 4 ilustra o mapeamento da Figura 1
utilizando essa técnica, onde cada objeto da hierarquia da Figura 1 possui uma classe
respectiva na Figura 4.
Figura 4. Mapeamento para cada classe
19
- Uma estrutura genérica de tabelas para todas as classes: Uma estrutura genérica não
atende a uma hierarquia de classes específicas, é um molde para as classes da aplicação. Cada
classe da Figura 1 é representada por uma linha dentro da tabela Classe na Figura 5. Os
relacionamentos, atributos e valores seguem a mesma ideia.
Figura 5. Mapeamento para cada classe
•
Agregação
Para representar agregação de objetos em tabelas relacionais, existem dois padrões:
- Agregação em uma tabela: os objetos agregados ao objeto principal têm seus
atributos inseridos na tabela relacionada com o objeto principal.
- Agregação com chave estrangeira: Os objetos são mapeados em tabelas diferentes, a
tabela do objeto principal se comunica com os objetos agregados através de chave estrangeira.
•
Associação
Objetos podem ter associações do tipo um-para-muitos (1:N) e muitos-para-muitos
(N:M). Para o caso de 1:N, a associação pode ser feita utilizando apenas chaves estrangeiras. O
objeto principal possui uma identificação e os objetos associados a ele possuem a mesma
20
identificação. No caso de N:M, é criada uma tabela para guardar as associações entre as duas
tabelas.
Para cada conceito de OO, existem alguns padrões para mapeamento, cada qual possui
vantagens e desvantagens. É preciso analisar o cenário para identificar qual padrão é o mais
adequado. Abstraindo-se disso, o uso desses padrões acarreta uma adaptação relevante entre os
dois paradigmas OO e relacional. Entretanto, isso requer alterações nas estruturas de tabelas
preexistentes no banco de dados. As empresas de desenvolvimento e seus clientes, em alguns
casos, optam por não alterar a base de dados buscando preservar a integridade dos mesmos. Os
dados são a base do sistema e, muitas vezes, vitais para o negócio do cliente. Perdas ou
inconsistência de dados podem fazer com que o sistema pare de funcionar acarretando em
grandes problemas para o cliente. Assim, a escolha pelo mapeamento sem modificar a base de
dados costuma ser a uma opção considerada na área.
1.4 Padrões de projeto
Como a implementação das soluções propostas será baseada em OO, torna-se
interessante a utilização de soluções reutilizáveis de software orientada a objetos conhecidas
como Padrões de Projetos ou Design Patterns (Gamma et al, 2005). Estes padrões definem
meios de como projetar uma solução para diversas situações, as quais, com a sua utilização, são
resolvidas de forma mais elegante e mais legível.
Gamma et al (2005) explica a dificuldade que se tem em projetar softwares orientados a
objetos de forma reutilizável. Enfatiza que os mais experientes projetistas afirmam ser difícil
ou até mesmo impossível projetar um software reutilizável e flexível em sua versão inicial.
Porém, os mais experientes tendem a realizar bons projetos, enquanto que projetistas iniciantes
se deparam com uma gama de possibilidades e acabam por, nem sempre, escolher a melhor
abordagem.
O que Gamma et al (2005) propõe é a catalogação das soluções obtidas em projetos na
forma de padrões de projetos. Assim, projetistas poderão obter o conhecimento de soluções que
21
deram certo e passar a utilizá-las, enquanto outros projetistas entenderão essas soluções, pois
também terão o conhecimento das mesmas através desse catálogo.
Os padrões de projetos foram divididos em três grupos de acordo com suas finalidades
(Gamma, et al, 2005):
- Criação: abstraem o processo de instanciação. Desacoplam o objeto do sistema. O
desenvolvedor não precisa saber exatamente qual objeto está sendo instanciado e sim como sua
interface funciona. Possibilita a manutenção do objeto escondido sem a interferência na
aplicação do usuário.
- Estruturais: ajudam na formação de estruturas maiores e mais complexas tanto com
classes quanto com objetos. Descrevem meios de como estruturar classes e objetos que foram
feitos separadamente para trabalharem juntos.
- Comportamentais: preocupam-se com a responsabilidade entre objetos. Permitem ao
desenvolvedor focar apenas na maneira em que os objetos são interconectados.
Metsker (2004) classifica os padrões em outros cinco grupos, conforme o problema:
- Interface: Auxiliam no desenvolvimento de interfaces.
- Responsabilidade: Objetos delegam suas responsabilidades para outros objetos. Com
esses padrões consegue-se extrair a complexidade de objetos e centralizá-las em outro,
facilitando ao desenvolvedor entender o que o objeto faz, abstraindo o como.
- Construção: Similar ao de criação de Gamma, abstrai o processo de instanciação do
objeto. O cliente do objeto não precisa saber qual é seu construtor concreto, pode trabalhar com
interfaces, utilizando padrões de construção evitando instanciar diretamente classes concretas.
- Operação: Controlar os métodos e operações dos objetos, os padrões ajudam no
desenvolvimento de objetos, facilitando a modelagem na utilização de métodos e operações de
outros objetos e tornando mais legível a utilização dos mesmos.
22
- Extensão: Padrões para tratar o acesso a uma hierarquia de objetos já existentes.
Projeções e técnicas para acessar informações de objetos com o intuito de alterar o mínimo
possível o código existente.
Os padrões de projeto serão importantes para o desenvolvimento do repositório de
soluções, o qual será descrito mais adiante. Conforme os problemas vão sendo analisados, os
padrões serão um guia para modelar as soluções de forma adequada.
1.5 Ferramentas ORM
Object Relational Mapping (ORM) são ferramentas ou frameworks complexos que
realizam o mapeamento do objeto no modelo relacional de forma automatizada e transparente
(Bauer, King, 2005). Algumas ferramentas reduzem significantemente o trabalho braçal dos
desenvolvedores para persistir os objetos.
Em Bauer et King (2005 p. 31-39) os autores fazem uma análise sobre a importância
das ferramentas ORM. Eles afirmam que as ferramentas trazem um ganho considerável em
termos de produtividade para o projeto. Afirmam ainda que, embora ocorra certa penalidade no
desempenho da aplicação, isso se torna válido visto o ganho em produtividade. Alegam
também que, desenvolver uma aplicação com bom desempenho para diversos tipos de banco de
dados é uma tarefa árdua e nem sempre se escolhem os comandos mais performáticos, o que
não ocorre em uma ferramenta ORM com anos no mercado. Esta possui conhecimento
suficiente para decidir qual o melhor comando para cada banco e, assim, compensar a questão
de desempenho perdido.
Embora as ferramentas ORM facilitem o trabalho do desenvolvedor em relação à
persistência de dados relacionais, é importante o conhecimento do mesmo sobre a relação de
suas tabelas no banco e sobre a linguagem SQL. Assim, é possível ter um código de maior
qualidade no acesso ao banco, pois o desenvolvedor ainda precisa especificar como será
realizado o acesso à base de dados.
1.5.1
Hibernate
23
Hibernate é uma das ferramentas ORM mais utilizadas no ambiente corporativo.
Atende a todos aos requisitos de uma ORM. Implementada no ambiente Java com código
aberto (OpenSource), o Hibernate provê uma arquitetura flexível e configurável (Bauer; King,
2005).
Essa ferramenta disponibiliza algumas maneiras de obter dados do banco, através de
uma linguagem de consulta própria (Hibernate Query Language ou HQL), parecida com SQL e
uma API de consulta por critérios (QBE), um modo seguro para expressar consultas. Assim, o
desenvolvedor não precisa se preocupar com uma linguagem diferente para cada tipo de banco.
A ferramenta, no entanto, aceita linguagem SQL, ideal para soluções complexas ou que
necessitem de um melhor desempenho.
Segundo Bauer et King (2005), um dos objetivos do Hibernate é automatizar 95% do
trabalho de persistência realizado pelo desenvolvimento sem uma ORM, resultando em um
ganho de produtividade, um requisito importante em grandes projetos.
A forma como deve ser feito o mapeamento das tabelas no modelo relacional para os
objetos da aplicação OO é definida através de arquivos de configuração escritos em XML. Em
alguns casos mais simples, o próprio Hibernate configura o mapeamento, porém Bauer afirma
que essa decisão é sensível, e pode não ter sucesso em casos com maior complexidade.
Existe também um projeto de código aberto similar ao Hibernate para o ambiente .NET
chamado NHibernate.
1.5.2
Ibatis
Ibatis é uma ferramenta utilizada em Java, Ruby e .NET. Oferece ao usuário uma
camada simples de separação entre a aplicação OO e o modelo relacional. O mapeamento dos
objetos é realizado através de documentos XML. A escrita dos documentos de configuração é
simples, não exige declarações complexas para unir tabelas, por exemplo, (IBATIS, on-line).
Essa ferramenta suporta diversos tipos de bancos de dados e tipos diversos de projeto
OO na aplicação são bastante tolerantes ao projeto do modelo das tabelas. Alguns frameworks
têm dificuldade em se integrar com projetos mal-elaborados, porém o Ibatis possui certa
tolerância a esse tipo de projeto. Podendo assim ser uma interessante solução para o caso em
24
que se tem uma aplicação OO tentando se comunicar com um banco de dados relacional
(IBATIS, on-line).
Para o auxílio da geração dos arquivos de configuração em XML e outros artefatos,
existe um produto chamado Ibator. Esse produto possui algumas formas de distribuições, uma
delas é como plugin para o Eclipse (IDE famosa para desenvolvimento em Java), onde, além
dos arquivos de configuração podem ser geradas classes de persistência em Java, e executáveis
.JAR caso o usuário utilize outra ferramenta além do Eclipse (Ibatis, on-line).
1.5.3 CocoBase
O CocoBase é um framework ORM para ambiente Java que oferece aumentos de até
25% de produtividade. O fabricante afirma ser o único framework ORM comercial com
certificação JPA (API de Persistência do Java) e que, em muitos casos, é mais eficiente que um
framework livre de código aberto. O fabricante afirma ainda ter cerca de 200% a 400% mais
desempenho que o Hibernate e outras ferramentas (CocoBase c, on-line).
No documento (CocoBase b, on-line) entende-se que o mapeamento realizado pelo
CocoBase é realizado de forma transparente e dinâmica. Transparente, pois seu mecanismo de
persistência não precisa herdar ou implementar classes diferentes e seus objetos e fontes ficam
intactos.
A arquitetura dinâmica do CocoBase permite que o mesmo mapeamento seja
compartilhado por: modelos de Objetos, Servlet, JSP, Applets, EJB e objetos Java. Isso implica
em diversos componentes distintos utilizando o mesmo mapeamento no banco de dados.
(CocoBase a, on-line).
A respeito dos artefatos, seus arquivos de configuração podem ser salvos em qualquer formato,
XML, binário, bancos de dados, etc. O CocoBase possui uma ferramenta chamada de Magic
Mapper que auxilia na criação dos mapeamentos automaticamente, não necessitando o
desenvolvedor criar um mapeamento manualmente para cada relação tabela / classe. Possui
aceitação com diversas ferramentas para ambiente Java, como Eclipse, no qual, através de um
plug-in, facilita-se a geração dos artefatos (CocoBase c, on-line).
25
2
Problemas Encontrados
Neste capítulo serão abordados alguns dos problemas encontrados na persistência de
objetos em um sistema relacional. Esses problemas foram separados em três categorias, cargas
de dados, mapeamento e descarga de dados, os quais serão detalhados seguidos de uma breve
descrição de como deve ser solução proposta nesse trabalho. As soluções serão inteiramente
discutidas no capitulo três.
O trabalho irá adotar em seus exemplos como tema principal o sistema acadêmico de
uma faculdade. Consiste em um sistema simples de cadastro de alunos e suas respectivas
disciplinas. Conforme os problemas vão sendo explorados o modelo de exemplo pode sofrer
adaptações.
O modelo padrão das tabelas do sistema de cadastro de alunos e disciplinas está
ilustrado no Figura 6. São basicamente três tabelas: ALUNO representando um objeto aluno,
DISCIPLINA representando um objeto disciplina e ALUNO_DISCIPLINA que representa o
relacionamento 1:N entre Aluno e Disciplinas.
26
Figura 6. Modelo de dados Aluno e Disciplinas
2.1 Carga de Dados
Os dados de uma aplicação estão mantidos normalmente em um servidor de banco de
dados, o qual, muitas vezes, está instalado em um computador diferente do servidor de
aplicação. Para que a aplicação possa manipular esses dados faz-se necessária a realização de
consultas no banco, através de algum tipo de linguagem específica para este fim, sendo a SQL
(Structured Query Language) a mais comum. Em seguida, devem-se preencher os objetos com
os respectivos dados retornados pela consulta.
2.1.1 Cargas Duplicadas
Em um sistema de grande porte, diversas consultas são criadas com o intuito de se
realizar operações necessárias ao funcionamento do sistema. Certas consultas podem trazer
dados já previamente carregados em memória por consultas anteriores. O que normalmente
ocorre é o preenchimento de um novo objeto com esses novos dados, o que acaba se tornando
uma réplica de outros objetos já existentes na memória. Objetos iguais mesmo em locais
diferentes na aplicação não precisam ter suas propriedades replicadas, podem ter suas
propriedades referenciadas pelos objetos.
27
Outro empecilho da duplicação dos dados é a dificuldade em se manter consistência
entre os dados. Alterações em propriedades não serão refletidas nas propriedades replicadas.
//Obtém os alunos do curso de computação
//Todos os objetos Aluno estão preenchidos com suas disciplinas
List<Aluno> alunos = ObterAlunosComputacao();
//Bloco de código utilizando os alunos
//Obtém disciplina LP1
Disciplina disciplina = ObterDisciplina(“LP1”);
//alterando o nome para Linguagens de Programação
disciplina.setNome(“Linguagens de Programação”);
//Código que deverá exibir todas as disciplinas de todos os alunos
//...
O código acima irá exibir a disciplina de LP1 como “LP1” e não “Linguagens de
Programação”, pois os objetos disciplina foram replicados. Uma solução comum é
carregar novamente do banco as disciplinas dos alunos, para recebê-las com a alteração.
A solução proposta é a criação de uma camada através da qual os retornos de todas as
consultas realizadas no banco de dados devem passar. Assim, é possível o gerenciamento do
acesso aos dados, o que se reflete nos objetos de negócio.
2.1.2 Carga Desnecessária de Dados
Frequentemente são necessárias todas as propriedades de um determinado objeto para
resolver um problema. Assim, quando esse objeto é carregado a partir do banco de dados
utiliza-se, desnecessariamente, a largura de banda da rede e a memória do servidor. Além
disso, dependendo de como as tabelas utilizadas na formação do objeto estejam organizadas,
buscas pelos dados de uma ou mais tabelas podem também ser evitadas.
Uma das técnicas utilizadas para solucionar esse problema é o uso do lazy load, a qual
consiste em carregar do banco os dados necessários para preencher certa propriedade do objeto
28
apenas quando a mesma é solicitada. Ou seja, são carregadas algumas informações essenciais
ao objeto e só são carregadas as demais propriedades quando as mesmas forem necessárias.
Métodos get são os casos onde normalmente se utiliza o lazy load. Caso a propriedade
esteja nula, é realizada a consulta para preencher esta propriedade e somente então o valor é
retornado pelo método. Essa é uma das maneiras mais simples de se implementar essa técnica
sem a utilização de frameworks de persistência.
O uso do lazy load precisa ser balanceado. Se muitas propriedades são chamadas
usando essa técnica, serão geradas muitas consultas desnecessárias ao banco. Nesse contexto,
um carregamento completo do objeto poderia ser uma opção mais interessante. Carregar
apenas uma vez e somente os dados que serão usados é o ideal, mas nem sempre é possível
saber quais dados serão necessários ou, quando se sabe, é preciso o desenvolvedor indicar tais
dados em algum lugar (arquivo de configuração, por exemplo), gerando um esforço
significativo no desenvolvimento de grandes aplicações.
Teoricamente, a melhor solução seria o programa sozinho prever quais dados serão
necessários e então chamar todos de uma só vez sem a necessidade do desenvolvedor informar
isso em algum local. Em situações simples isso é possível salvando um log com as
propriedades utilizadas em determinadas partes do programa. Porém, programas podem ser
muito dinâmicos e nem sempre as propriedades que foram utilizadas em um determinado local
serão as mesmas em outra execução do programa.
A solução proposta neste trabalho envolve algumas práticas a serem detalhadas, uma
delas é a utilização de auto-aprendizagem com logs, junto com estatísticas para carregar os
dados da melhor forma possível através de uma média de acesso aos dados a fim de tratar o
problema de dados dinâmicos.
2.1.3 Tráfego de Carga Com Dados Redundantes
Para preencher um objeto complexo (objeto que possui outros objetos e regras dentro de
si), normalmente é preciso acessar várias tabelas. Se apenas uma consulta for realizada para
preencher esse objeto, haverá vários dados redundantes, pois os dados para preencher os
objetos pai estarão se repetindo em todos os registros acompanhando os dados dos objetos
filhos ou coleções, conforme pode ser visualizado na Tabela 1.
29
Tabela 1: Um select retornando os dados de um aluno
ALUNO_NOME
ALUNO_MATRICULA
ALUNO_CURSO
DISCIPLINA_NOME
Thiago Benega
123456
Computação
Programação 1
Thiago Benega
123456
Computação
Estrutura de dados 1
Thiago Benega
123456
Computação
Redes 1
Por outro lado, essa redundância pode ser evitada através da realização de várias
conexões em vez de apenas uma. Assim, cada objeto contido dentro de um objeto complexo
seria preenchido por vez, para depois consultar e preencher o objeto complexo.
As duas abordagens prejudicam a rede que conecta o banco de dados com o servidor de
aplicação. A primeira consome largura de banda desnecessariamente transportando dados
redundantes, e a segunda realiza várias chamadas ao banco em vez de apenas uma.
A solução proposta consiste em carregar todos os dados necessários de uma vez e não
transportá-los de forma tabular (como visto na Tabela 1), e sim na forma de objetos, fazendo a
conversão dos dados antes de enviar para a aplicação.
2.1.4 Consultas Polimórficas Ou Hierárquicas
Na seção 1.3 deste trabalho foi descrita a dificuldade de se representar uma estrutura
hierárquica de classes nas tabelas do banco de dados relacional e suas respectivas técnicas para
lidar com o problema.
Consultas polimórficas são buscas por entidades de mais alto nível dentro de uma
estrutura hierárquica. Considere uma estrutura onde temos dois tipos de classes relativas aos
alunos: Aluno_de_Colegio e Aluno_de_Faculdade, ambos herdando de Aluno, a
qual, por sua vez, possui uma propriedade nome. Uma consulta por todos os alunos de nome
João é considerada polimórfica, pois trará tanto alunos de colégio quanto de faculdade.
(BAUER; KING, 2005)
A forma como a consulta deve ser criada para realizar esse tipo de consulta depende de
qual técnica foi utilizada para mapear a estrutura hierárquica no banco de dados. Com exceção
da técnica Tabela única para toda estrutura hierárquica, (seção 1.3) as outras técnicas utilizam
30
mais de uma tabela para representar a estrutura hierárquica. Assim para realizar esse tipo de
consulta é necessária a utilização de operações de uniões para trazer todos os resultados em
uma única consulta ou realizar várias consultas de acordo com o número de tabelas. Segundo
Bauer e King (2005) o Hibernate não tem suporte para utilizar uniões nesse contexto, sendo
necessário realizar várias consultas ao banco.
Como já discutido nesse trabalho, o uso de várias consultas tem suas desvantagens. A
dificuldade encontrada aqui é a execução desse tipo de consultas com simplicidade, porém com
desempenho adequado e a possibilidade de trazer os dados com apenas uma consulta.
Solução proposta: Ter uma estrutura que facilite o trabalho do desenvolvedor, porém
sem ocasionar limitações, ser aberta de forma que o desenvolvedor possa realizar o
mapeamento e a consulta da forma como desejar.
2.2 Mapeamento
Mapear, no contexto desse trabalho, consiste em preencher o objeto de negócio a partir
de um banco de dados relacional. As propriedades do objeto são relacionadas com as colunas
das tabelas no banco de dados. O objeto Aluno tem sua propriedade Nome relacionada com a
coluna NOME da tabela ALUNO, por exemplo. A relação entre propriedade do modelo OO e
coluna do Modelo Relacional não precisa ser 1:1, podendo ser N:M. Uma propriedade pode ser
a junção de duas ou mais colunas e vice-versa. Porém, o mais comum é a relação 1:1 e esta
será a mais abordada no trabalho.
2.2.1 Tabelas Com Colunas de Nomes Iguais
Um objeto pode ser formado a partir de várias tabelas no banco de dados. Trazer todos
os dados em uma única consulta pode gerar alguns conflitos de nomes, pois diferentes tabelas
podem conter colunas de mesmo nome. Assim o retorno da consulta deve diferenciar quais são
os atributos de cada tabela. Ex: as tabelas ALUNO e DISCIPLINA possuem uma coluna
chamada NOME.
A solução proposta é a utilização de um prefixo de forma a diferenciar e garantir a
unicidade do nome da coluna.
31
2.2.2 Campos de Retorno Dinâmicos
A solução de carga parcial utilizando o lazy load proposta nesse trabalho usa o conceito
de retornar os campos que estão sendo mais requisitados, assim as colunas escolhidas para
carregar o objeto precisam estar na consulta. Assim temos duas opções: retornar todas as
colunas ou deixar o retorno de colunas dinâmico.
Retornar todas as colunas seria um desperdício de memória e banda. A solução mais
interessante em termos de desempenho é o retorno de colunas dinâmico.
Solução proposta: O desenvolvedor não deve então informar as colunas de retorno,
estas devem ser escritas dinamicamente pelo sistema de acordo com a necessidade da lógica do
lazy load. As duas soluções precisam se comunicar para o funcionamento completo.
2.2.3 Acoplamento Com o Banco de Dados
Existem soluções no mercado que tornam a aplicação acoplada à estrutura do banco de
dados. Caso ocorra alguma mudança nas tabelas os objetos da aplicação precisam ser
modificados para aceitar a nova estrutura. Algumas soluções apenas necessitam mudar
arquivos de configuração (ex: hibernate e Ibatis), que definem o mapeamento objeto – tabela.
Mesmo com os arquivos de configurações alterados para comportar as mudanças das
tabelas, muitos arquivos ou classes com consultas, inserções, atualizações e deleções, criados
conforme o modelo de dados antigo, terão de mudar os nomes das tabelas e colunas. Isso pode
ser bastante trabalhoso.
Os objetos da aplicação provavelmente sofrerão modificações também. Seja inclusão ou
deleção de propriedades ou alteração na lógica de manipulação das propriedades.
Um exemplo de modificação simples que necessita de alteração no objeto:
Suponha-se que a tabela ALUNO retirou a coluna NOME e criou as colunas
PRIMEIRO_NOME, SOBRENOME. O objeto Aluno possui os métodos getNome e
setNome, os quais terão que implementar uma lógica para manipular as duas colunas ou
então criar métodos getPrimeiroNome , getSobrenome etc.. Porém, isso pode resultar
em modificações maiores dentro da aplicação.
32
Resumindo: modificações feitas no banco que impliquem na aplicação indicam um
acoplamento da aplicação com o modelo de dados. A solução proposta vai além de simples
arquivos de configuração. Ter uma camada intermediária entre os objetos e as tabelas. Dessa
forma, as mudanças nas tabelas exigem alterações, até certo ponto, apenas dessa camada
intermediária.
2.3 Descarga de Dados
A descarga consiste em persistir os dados manipulados pelos objetos de negócio da
aplicação no banco de dados. Utiliza os critérios de mapeamento pré-definidos entre os objetos
e suas respectivas tabelas para poder salvar os dados dos objetos.
2.3.1 Descarregar Apenas o Necessário
Um objeto muito complexo é comumente alterado apenas em parte de suas
propriedades. Executar uma atualização no banco de dados com todas as propriedades é
desperdício de banda da rede, visto que propriedades inalteradas serão trafegadas pela rede que
conecta a aplicação com o banco, e, dependendo do SGBD, terá uma sobrecarga desnecessária.
Esse tipo de problema a maioria dos frameworks ORM resolve, o problema é que
alguns SGBDs possuem melhor desempenho quando todas as colunas são atualizadas, mesmo
que apenas uma tenha sido alterada. O hibernate trata as duas formas, escolhendo a que for
melhor pro SGBD em questão. (BAUER; KING, 2005).
O caso onde o SGBD tem melhor desempenho com a atualização de todas as colunas, a
rede, no entanto, será penalizada, pois irá carregar dados que não foram atualizados. Neste caso
o ideal seria alcançar o melhor desempenho do SGBD sem penalizar a rede.
Solução proposta: ter uma estrutura no lado do servidor de banco de dados, e outro no
lado da aplicação, então controlar mudanças nas propriedades dos objetos para poder ser
enviado para estrutura do lado do banco apenas as colunas alteradas, e esta por sua vez altera
todas as colunas no banco.
33
2.3.2
Descarregar Apenas Quando Necessário
No desenvolvimento da aplicação, cabe ao desenvolvedor informar os pontos em que os
dados devem ser salvos no banco. O comando commit deve ser executado quando se deseja
garantir que tudo já enviado ao banco ocorreu com sucesso e deve ser persistido.
Caso algum erro ocorra na aplicação, o comando commit não será executado, e sim um
rollback o qual desfaz todas as alterações no banco realizadas durante a transação. Em casos
como este, todos os comandos enviados foram apenas desperdício de banda de rede e de
processamento no banco de dados.
Outra questão a ser analisada é que durante a execução do código o desenvolvedor pode
executar alterações no banco em partes diferentes do programa e, muitas vezes, os dados não
necessitavam ser inseridos no banco no exato momento. Pode ser mais interessante para a
aplicação e o banco de dados, executar todos os comandos de uma vez só. No entanto, realizar
essa lógica manualmente exige um esforço a mais do programador, o qual precisa analisar se os
dados a serem inseridos no banco serão utilizados em alguma consulta futura ou não, porém ao
longo do desenvolvimento o programador deve rever se essa questão se mantém diante das
alterações.
Solução proposta: Manter os dados na camada entre aplicação e banco de dados até que
um commit seja executado ou alguma consulta necessite acessar alguma tabela que esteja
sendo mantida na camada.
34
3 REPOO (Repositório Orientado a Objetos)
Esse capítulo descreve o que vem a ser o Repositório Orientado a Objetos, a base para
solucionar os problemas encontrados e catalogados no capítulo dois deste trabalho.
O REPOO não é um framework ORM ou uma biblioteca. É uma sugestão, ou guia de
implementação que tenta tratar de uma forma diferente alguns problemas relacionados com o
relacionamento entre persistência de dados relacionais com orientação a objetos. A ideia base
do REPOO é ser uma camada de transição entre aplicação (OO) e o banco de dados
(relacional). Essa camada tenta representar as colunas e os relacionamentos entre as tabelas de
forma orientada a objetos. As colunas se unem formando uma rede, deixando de se representar
de forma tabular.
A Figura 7 exemplifica a representação de um cenário entre as três camadas, temos um
aluno e suas respectivas disciplinas. Na camada relacional representa-se isso através de três
tabelas, ALUNO, DISCIPLINA e ALUNO_DISCIPLINA que mantém o relacionamento entre
um aluno com suas respectivas disciplinas, utilizando os conceitos de chave estrangeira. Na
camada
do
REPOO
temos
dois
Containers,
ContainerAluno
e
ContainerDisciplina, os containers mantém os objetos Campos que fazem o
mapeamento com as colunas das tabelas, dentro de AlunoContainer tem um objeto do tipo
CampoList (Disciplinas), este possui uma lista de ContainerDisciplina e faz o
mapeamento e tratamento da junção no modelo relacional. Na camada da aplicação, temos o
objeto Aluno que possui uma lista de objeto disciplina. Estes objetos, como já citado, não
possuem propriedades, e sim métodos que fazem uso dos Campos relacionados.
35
Figura 7. Camada REPOO
A camada do REPOO, diferentemente das soluções comuns encontradas no mercado,
não é apenas um mapeamento entre propriedades dos objetos e colunas das tabelas do banco, e
sim uma camada intermediária que suporta implementação por parte do desenvolvedor. Nas
soluções ORM é comum a utilização de arquivos de configuração para realizar o mapeamento
36
entre objetos e tabelas, não apenas relacionamentos simples, relacionamentos um para muitos
mapeando uma lista de objetos também são aceitos. Porém tais arquivos de configuração são
limitados ao que a ferramenta oferece, a camada do REPOO permite implementação, podendo
retirar qualquer tipo de lógica de persistência dos objetos de negócio.
Em Padrões de Projetos é visto que objetos podem delegar suas responsabilidades para
outros melhorando o entendimento, a legibilidade e manutenibilidade do código. O REPOO
permite que a complexidade de persistência de dados seja extraída dos objetos da aplicação,
pois os mesmos não possuem atributos ou propriedades dentro de si, apenas métodos de acesso
gets e sets que fazem referência aos objetos na camada do REPOO.
A Figura 8 mostra o relacionamento entre as três camadas, Aplicação, REPOO e Banco
de dados. Na camada de aplicação temos os objetos, que por sua vez possuem propriedades, em
boas práticas de OO as propriedades são privadas e podem ser acessadas por métodos,
get<nome da propriedade> e set<nome da propriedade>. Neste trabalho os objetos apenas
possuem os métodos de acesso, suas propriedades são os objetos Campo, assim quando é dito
que uma propriedade se relaciona com determinados objetos Campo, significa que os métodos
de acesso manipulam diretamente os objetos Campo relacionados.
Na camada do REPOO temos os objetos Campo, os mesmos guardam os valores
manipulados pelos objetos da aplicação e no momento certo realizam a persistência destes
dados nas colunas relacionadas no banco de dados. O contrário também ocorre, os dados das
colunas são carregados do banco para os determinados objetos Campo e os objetos da
aplicação fazem uso dos mesmos.
37
Figura 8. Propriedades, Campos e Colunas
Vale ressaltar que uma propriedade pode se associar a um ou mais objetos Campos, os
quais podem se associar com uma ou mais colunas no banco de dados. Uma chamada a
determinada propriedade de um objeto é feita através do método get<nome da propriedade>, o
qual irá buscar pelo objeto Campo associado. Dentro de Campo pode existir uma simples
associação para uma coluna na tabela, ou alguma manipulação com várias colunas no banco
que geram e retornam um dado para quem chamou o método get.
Com a estrutura de camada do REPOO é possível trocar de banco de dados ou mudar a
estrutura das tabelas, ocasionando pouca ou nenhuma alteração nos objetos da aplicação, tendo
apenas que alterar o mapeamento entre o Repositório e o Banco de Dados. O mapeamento
entre os objetos e o REPOO, assim como os próprios objetos, podem ser preservados.
3.1 Classes
O REPOO é uma abstração de uma arquitetura para resolução dos problemas
catalogados neste trabalho, suas principais classes e respectivos funcionamentos são descritas
nesta seção.
•
CampoAbstrato
Classe abstrata que possui métodos get e set que devem ser implementados pelas
classes que a herdam.
38
•
CampoID
Herda de CampoAbstrato e faz a associação entre a propriedade ID de determinado
objeto com o ID de determinada tabela.
•
Campo
Herda de CampoAbstrato e faz a associação entre propriedades de determinado
objeto com as colunas de determinada tabela. Possui uma referência para um CampoID, que
identifica qual a tabela e o registro ao qual o Campo deve se associar.
•
CampoList
Herda de CampoAbstrato, faz a associação entre uma propriedade do tipo lista de
determinado objeto com determinadas tabelas.
•
Container
Uma classe abstrata de agrupamento que possui uma lista de objetos do tipo
CampoAbstrato e os mesmos são acessados pelo objeto da aplicação por intermédio do
Container.
•
Query
Classe abstrata responsável pela criação de consultas personalizadas. Para criar uma
consulta personalizada, escrita utilizando a linguagem SQL, deve-se criar uma nova classe
herdando de Query e implementar o método Consultar(object[] params) com o
novo SQL desejado.
Ao instanciar a classe para executar a consulta, os objetos Containers que tiverem
relação com a consulta devem ser passados para o construtor da classe, afim de preencher os
objetos do tipo CamposAbstratos contidos nos Containers.
•
Transação
39
Classe utilizada para controlar as transações realizadas durante a execução da aplicação.
Ao ser iniciada e finalizada são gerados logs com alguns dados utilizados pelo REPOO.
3.2 Arquivos
•
LogTransação
Arquivo de log utilizado por determinada transação. Nele são gravadas algumas
informações como número de vezes que determinado objeto Campo foi acessado.
É utilizado para cálculos de probabilidade do REPOO para carregar ou não um objeto
Campo. O nome do arquivo é montado de acordo com o nome da transação:
LogTransação<nome-transação><data atual (yyyyMMdd)>.log.
•
ConfigTransações
Arquivo de configuração geral para todas as transações. Os parâmetros necessários para o
funcionamento do REPOO estão contidos nesse arquivo. É possível o desenvolvedor criar seus
próprios parâmetros.
•
ConfigTransação
Arquivo de configuração por determinada transação. Sobrescreve os parâmetros de
ConfigTransações para determinada transação. Utilizado quando alguns parâmetros de
transação globais não atendem à transação em questão.
O
nome
do
arquivo
é
montado
de
acordo
com
o
nome
da
transação:
ConfigTransação<nome-transação>.cfg.
3.3 Funcionamento
Nesta seção serão descritos o funcionamento e a usabilidade do REPOO. Quando se trata
de persistência e manipulação de dados, têm-se três áreas bem definidas, consulta ou carga de
dados, manipulação e persistência dos dados e exclusão. Assim esta seção tratará essas três
subdivisões.
40
Antes de tudo, a execução do REPOO começa com sua instanciação e então a inicialização
de uma transação. O início e fim de uma transação garantem que tudo ocorreu bem e os dados
podem ser persistidos no banco. Para o Repositório assim como no hibernate (BAUER; KING,
2005), é trabalho do desenvolvedor informar explicitamente quando se é iniciada e finalizada
uma transação.
//É utilizado o Padrão Singleton para garantir que exista apenas
//um objeto do tipo REPOO na aplicação
Repoo repoo = Repoo.getInstance();
//Inicia a transação
Transacao transacao = repoo.iniciar(“nome da transação”);
//bloco de código qualquer
//...
//Finaliza a transação
transacao.finalizar();
3.3.1 Consulta e Preenchimento
A consulta e o preenchimento dos objetos da aplicação podem ser feitos de duas maneiras:
chamando o construtor do objeto requerido ou executando uma consulta (classe Query).
Sabe-se que os objetos de negócio não possuem propriedades, as mesmas são acessadas na
camada do REPOO. Dessa forma ao se executar as consultas no banco, o que é preenchido na
verdade são os objetos Campos que estão associados aos objetos da aplicação.
Os objetos precisam estar associados corretamente ao REPOO e por essa razão não são
instanciados por seus construtores padrão, o REPOO utiliza o padrão de factory method para
criar e retornar o objeto requisitado corretamente.
Aluno aluno = (Aluno) Repoo.getObjeto(Aluno.class);
Aluno alunoJoao = (Aluno) Repoo.getObjeto(Aluno.class, 1);
41
O método getObjeto pode criar um novo objeto ou carregar do banco. O segundo
parâmetro recebe o id do objeto, no caso do exemplo acima o objeto aluno foi criado sem
carregar nada do banco, e o objeto alunoJoao foi carregado do banco com o id 1.
A outra forma de carregar objetos é utilizando as consultas predefinidas pelo
desenvolvedor.
//Obtendo todos os alunos do curso de “Computação”
List<Aluno> alunos = (List<Aluno>) Repoo
.consultar(QryAlunosPorCurso.class, “Computação”);
A classe QryAlunosPorCurso possui uma consulta SQL buscando por alunos pelo
nome do curso, que é passado por parâmetro.
Ao implementar a consulta em QryAlunosPorCurso as colunas não são informadas
pelo desenvolvedor, as mesmas serão geradas dinamicamente pelo REPOO, os critérios para
geração das colunas serão explicados mais a frente. O SQL seria algo como:
“SELECT
AlunoContainer FROM ALUNO WHERE ALUNO.CURSO = :curso”
Os objetos Campos são preenchidos de acordo com as colunas de retorno na consulta SQL,
isso ocorre nos dois métodos de obtenção de objetos falado acima. As colunas são geradas
dinamicamente e os critérios para uma coluna ser gerada são de acordo com sua necessidade
para a aplicação. É criado um log para cada transação existente na aplicação. Todas devem
possuir nomes diferentes. Neste arquivo de log fica gravado quantas vezes determinado Campo
foi executado, que seguindo um cálculo de probabilidade e balanceamento o REPOO decide se
as colunas necessárias para preencher o objeto Campo irão ser geradas ou não. A seção 4.1.2
detalha melhor o funcionamento do cálculo.
Caso a coluna não seja gerada e for necessária para preenchimento de um objeto Campo
chamado pela aplicação, o mesmo irá se carregar automaticamente, gerando um novo acesso ao
banco.
Esse meio de carregamento de dados é conhecido como lazy load. As ferramentas mais
comuns de ORM fazem o uso dessa técnica, o hibernate pode fazer ou não o uso da mesma. O
42
que diferencia o REPOO das outras ferramentas é que nas outras as colunas que devem ser
carregadas com lazy devem ser informadas através de arquivos de configuração, no REPOO as
mesmas são calculadas, tirando a necessidade do usuário informá-las.
A estrutura do arquivo de log é em XML:
<nome-transação > </nome- transação >
<execuções-transação ></execuções-transação>
<percentual-execuções-transação></percentual-execuções-transação>
<container nome>
<propriedade nome=>
<execuções> </execuções>
</propriedade>
...(mais propriedades se necessário)
</container>
<container nome=>
</container>
Nome
do
arquivo:
LogTransacao<nome
da
transação><data
yyyyMMdd>.log.
Essa mecânica de funcionamento do REPOO visa à economia nos acessos ao banco de
dados. Outra funcionalidade que visa o mesmo objetivo é a unicidade dos Campos. No
Repositório, sempre quando ocorre uma consulta ao banco de dados, na etapa de extração dos
dados para os objetos Campos, é feita uma comparação para saber se os valores retornados já
existem no Repositório, afim de desconsiderar e trazer menos dados da consulta. São
comparados os CamposIDs existentes com os IDs retornados pela consulta, caso sejam iguais
são carregados apenas os Campos das colunas novas ao Repositório.
//Carrega o aluno com id 1
Aluno aluno = ObterAluno(1);
//A consulta preencheu apenas os Campos Nome e Matricula
//Consulta todos os Alunos, carregando Nome e Idade
List<Aluno> alunos = ObterTodosAlunos();
43
//Quando for carregar os Campos do Aluno com Id = 1,
//apenas Idade será carregada
//Pois Nome já existe no Repositório
3.3.2 Persistência
A persistência dos dados no banco ocorre em dois momentos, na finalização de uma transação
ou quando alguma consulta necessita antes que os dados sejam persistidos no banco. No
momento de persistir os dados é verificado se determinado Campo será inserido ou atualizado
na tabela, caso o Campo possua um ID (CampoID) com valor igual a zero, é realizado uma
inserção (comando insert em SQL), seguida de uma consulta para obter o ID gerado pelo
banco, caso contrario é realizado uma atualização (comando update em SQL).
A persistência dos dados no banco é feita na finalização da transação. Todos os Campos
que tiverem seu respectivo CampoID com valor diferente de zero são inseridos no banco de
dados por meio de inserts, os que tiverem valor acima de zero são atualizados no banco por
meio de updates. O desenvolvedor não precisa estar declarando SQLs de inserção ou
atualização, isso é feito de forma automática.
3.3.3 Exclusão
Os objetos da aplicação herdam de ObjetoREPOO e assim possuem um método
chamado Deletar, ao invocar esse método é informado ao Repositório quais campos referentes
ao objeto deletado devem ser excluídos no banco. Por padrão, todos os dados filhos
relacionados com o objeto são removidos, porém o método remover() pode ser sobrescrito
caso o desenvolvedor necessite em alguma situação diferente. Podendo modificar o código
SQL.
44
4 Soluções Propostas
Neste capítulo serão abordadas as soluções para os problemas descritos no capítulo
dois.
A base das soluções aqui proposta é a utilização do REPOO. Este repositório é uma
camada intermediária entre o banco de dados e a aplicação. Este terá uma estrutura similar a de
um banco de dados relacional, porém com a intenção de ser uma estrutura organizada a favor
de OO, facilitando a comunicação da aplicação com o repositório e mapeando as diferenças
com o banco de dados. Com essa estrutura, é possível trocar de banco, de paradigma, podendo
haver mudança na estrutura das tabelas, tendo apenas que alterar o mapeamento Repositório e
Banco de Dados.
Os objetos da aplicação não irão possuir atributos ou propriedades relacionadas ao
banco dentro de si, mas terão apenas métodos de acesso get e set.
4.1 Cargas
Nesta seção serão abordadas as soluções para os problemas relacionados com cargas
definidos na seção 2.1 deste trabalho.
4.1.1 Cargas Duplicadas
A forma como é implementado o repositório, retirando as propriedades de dentro do
objeto, tenta tratar o problema de Cargas Duplicadas. Independentemente de quantos objetos
com mesmo ID sejam criados, as propriedades não serão duplicadas, pois estão centralizadas
no Repositório. Dessa forma, o desenvolvedor abstrai o controle de memória e criação de
objetos em relação as suas propriedades, desprezando o custo mínimo de memória que é usado
por novas instanciações de objeto.
45
Isso é interessante em questões onde existem objetos muito utilizados por outros objetos.
Suponha-se, por exemplo, que o objeto disciplina tenha muitas propriedades, todos os
alunos possuem disciplinas e muitos deles têm disciplinas iguais. Assim, ao realizar uma
consulta com vários alunos, sem o uso do Repositório, serão criados vários objetos
disciplina iguais e repetindo suas propriedades. Em larga escala, isso pode ser prejudicial
para o sistema.
Outra vantagem de se evitar duplicação de propriedades é na manutenção dos dados. Se
um mesmo objeto de negócio é duplicado, alterações em uma cpóa não refletem nas outras
cópias.
4.1.2 Carga Desnecessária de Dados
O uso do lazy load trata os problemas relacionados com o carregamento de dados
desnecessários, trazendo para a aplicação apenas os dados que serão utilizados em determinado
momento.
O REPOO é a camada onde essa lógica é centralizada. Os objetos de negócios não são
carregados usando o lazy load e sim o repositório. A camada do REPOO guarda um log com as
principais propriedades requisitadas pelos objetos no contexto daquela transação. As
propriedades mais acessadas são carregadas de uma só vez no Repositório. Existe também um
cálculo de probabilidade e balanceamento que decide se uma propriedade deve ser carregada
ou não. O funcionamento deste cálculo será detalhado mais adiante.
Quando uma propriedade é chamada, é incrementada uma unidade no contador de
execuções na sessão Propriedade dentro do arquivo LogTransação<Nome
da
Transação><Data>.log:
<nome- transação >ConsultaAluno</nome- transação >
<execuções-transação > 10 </execuções-transação>
<percentual-execuções-transação>
50
</percentual-execuções-
transação>
<container nome=Aluno>
<propriedade nome=Nome>
46
<execuções> 9 </execuções>
</propriedade>
<propriedade nome=Matricula>
<execuções>1</execuções>
</propriedade>
</container>
LogTransacaoConsultaAluno20100415.log.
O cálculo consiste em: (execuções-transação / execuções) / 100. Assim se obtém o
percentual de vezes que determinada propriedade foi chamada. Caso esse número seja maior ou
igual a X a propriedade é carregada do banco, sendo X um valor definido pela variável
percentual-execuções-transação
que
pode
ser
definida
em
dois
pontos:
em
ConfigTransação<Nome da Transação> (específico para aquela transação) ou no arquivo de
configuração do repositório (para todas as transações) ConfigTransações.
No exemplo da transação ConsultaAluno, apenas a propriedade Nome será carregada,
pois percentual-execuções-transação está definida com valor 50% e em 10 execuções
(execuções-transação) a propriedade Nome foi carregada 9 vezes, totalizando 90%, enquanto
que a propriedade Matricula foi carregada uma vez, totalizando 10%. As demais
propriedades não foram carregadas nenhuma vez.
Essa forma de funcionamento por logs permite que o sistema se adapte conforme sua
utilização. Uma funcionalidade da aplicação pode, em determinado momento, realizar diversas
chamadas a uma propriedade e em outra não mais.
Como exemplo, suponha-se um relatório que imprima uma lista com alunos menor de
idade.
if(aluno.Idade < 18)
{
System.out.println(aluno.Nome);
}
Em determinado semestre da faculdade todos os alunos são maiores de idade, porém no
semestre seguinte entraram muitos estudantes com menos de 18 anos. O sistema, a principio,
47
não carregou nenhum Nome para aluno, porém no semestre seguinte, conforme Nome estava
sendo requisitado, o mesmo passou a ser sempre carregado.
Para garantir que essa mudança de comportamento ocorra rapidamente, deve-se informar
no arquivo de configuração geral (ou da respectiva transação) o número de dias que devem ser
levados em consideração. Por exemplo, dez dias significa que o REPOO irá considerar o
número de execuções dos arquivos de log da data atual até dez dias atrás. Assim, o sistema
identificará uma mudança de comportamento com maior facilidade, evitando que se passe, por
exemplo, um semestre inteiro carregando Nome por lazy-load (indiretamente) até que o valor
de chamadas de Nome supere as não chamadas em relação ao tempo total da aplicação.
O Hibernate trata o problema de cargas desnecessárias de uma forma estática. O usuário
deve escolher entre quatro técnicas. O desenvolvedor deve ter conhecimento do negócio, do
banco de dados e de orientação a objetos para decidir qual técnica oferece o melhor
desempenho para determinada situação. No entanto, não existe tratamento para o problema de
dinamismo da aplicação, no qual a necessidade do uso de determinadas propriedades pode
mudar. Em determinado momento uma das técnicas pode ter melhor desempenho que outra.
(Bauer; King, 2005)
O Repositório proposto neste trabalho permite que sejam indicadas no arquivo de
configuração de determinada transação as propriedades que sempre ou nunca devem ser
carregadas, de forma a permitir certa flexibilidade para garantir um melhor desempenho em
determinadas situações.
4.1.3 Tráfego de Cargas Redundantes
Carregar vários dados para o Repositório em uma única consulta fará com que a rede
trafegue muitos dados redundantes na comunicação entre os servidores de banco de dados e da
aplicação.
Este problema deve ser tratado no servidor que hospeda o banco de dados. Uma
aplicação deve realizar a troca de informações entre a aplicação (utilizando o repositório) e o
banco de dados, retornando para o repositório da aplicação a consulta em uma forma não
tabular.
48
O Repositório instalado no servidor de dados é uma outra parte da solução proposta, as
trocas de informações para manipulação de dados entre as duas seria através dos objetos já
comentados nesse trabalho, os Campos. Através deles podemos trafegar uma estrutura de rede,
minimizando a quantidade de dados transmitidos, o que não é possível com a estrutura tabular.
Exemplo: na seção 2.1.3 a Tabela 1 representa um retorno com estrutura tabular onde as
colunas ALUNO_NOME, ALUNO_MATRICULA, ALUNO_CURSO se repetem, apenas
DISCIPLINA_NOME contém registros diferentes, essa estrutura pode ser representada por
objetos Campos, assim existiria por exemplo, apenas um Campo para o nome do aluno.
4.1.4 Consultas Polimórficas Ou Hierárquicas
Sabe-se que o REPOO é uma camada entre a aplicação OO e o banco de dados
relacional. Sua representação das colunas das tabelas é feita através da ligação dos objetos
Campos. Os mesmos podem ser modelados a favor dos objetos da aplicação. Através dessa
modelagem e representação dos dados é possível implementar dentro da camada do REPOO o
mapeamento que o desenvolvedor achar necessário.
As técnicas descritas por AMBLER (on-line) na seção 1.3 deste trabalho são passíveis de
serem utilizadas pelo REPOO. Isso tudo é possível com a utilização de apenas uma consulta,
ficando a escrita do código SQL da consulta a cargo do desenvolvedor.
Cada objeto Campo possui suas regras de carregamento, e os mesmos irão extrair os
dados da consulta de acordo com tais regras. Tudo isso é livre para o desenvolvedor
implementar da melhor forma para determinada situação.
4.2 Mapeamento
O mapeamento é realizado em duas etapas. Existe o mapeamento relacionando o
Repositório com as tabelas do banco de dados, e o mapeamento entre o Repositório e os
objetos de negócios.
O Repositório é um conjunto de objetos chamados Campos; tais objetos estão associados
a um Campo ID, que é o objeto que possui o ID que referencia a tabela no banco.
49
Os campos são agrupados de acordo com seu escopo de objeto de negócio. Tais
agrupamentos são armazenados em objetos chamados Containers. Objetos de negócio vão
procurar seus campos dentro de seu relativo container.
Aluno, um objeto de negócio, referencia um AlunoContainer que agrupa os objetos
Campos: Nome, Mátricula, Curso, que estão associados ao CampoID que referencia a tabela
ALUNO. Dentro de AlunoContainer há ainda Disciplina, um objeto do tipo ListaCampo,
que está associado aos objetos CampoID que referenciam as tabelas: ALUNO, DISCIPLINA e
ALUNO_DISCIPLINA. Esta é a maneira que o Repositório trata listas de valores associados a
uma tabela.
4.2.1 Tabelas com Colunas de Nomes Iguais
Nos objetos Campos, são armazenadas informações como o nome da tabela e nome da
coluna. Dessa forma é possível utilizar um alias único <nome da tabela>_<nome da coluna>, e
os valores serão sempre obtidos assim, garantindo colunas com nomes únicos.
Na construção das consultas, não são informadas as colunas que se deseja retornar. Isso
é realizado pelo Repositório, onde entra o controle de cargas. Assim o próprio Repositório cria
essa estrutura de nomes, de forma transparente ao desenvolvedor.
Essa técnica é utilizada internamente pelo Repositório, o desenvolvedor não escreve
cada aliase nas consultas, pois os dados são carregados para os objetos de forma transparente.
4.2.2 Campos de retorno dinâmicos
A forma como os dados são carregados do banco para o Repositório se dá através da
construção e execução das consultas. Os campos de retorno não são informados pelo
desenvolvedor, sendo definidos pelo sistema de acordo com os objetos Container que são
passados como parâmetro para a consulta. As colunas que serão carregadas pela consulta serão
as referentes aos objetos Campos do Container que, através do cálculo de probabilidade e
balanceamento, são mais utilizadas dentro da transação na qual a consulta está sendo
executada.
50
4.2.3 Acoplamento com o Banco de Dados
O Repositório funciona como uma camada entre a aplicação e o banco de dados. Pelo
fato de se ter uma camada intermediária entre esses dois paradigmas, a coesão entre os dois
tende a diminuir, pois pode ser acrescentado lógica para manipular os dados fora dos objetos da
aplicação. A aplicação se comunica com o REPOO e o mesmo se comunica com o banco de
dados. Algumas mudanças no banco que não afetem a lógica de negócios podem ser realizadas
sem alterar os objetos da aplicação, apenas alterando o REPOO.
Na seção 2.2.3 o exemplo do problema da tabela ALUNO que mudou a coluna NOME
para duas colunas, PRIMEIRO_NOME e SOBRENOME, foi resolvido implementando lógica
dentro do objeto da aplicação, como no REPOO os objetos se comunicam diretamente com as
propriedades dele, a lógica é desacoplada do objeto e assim implementada dentro do Campo
Nome em AlunoContainer.
O objeto Aluno chama o método getNome() e o mesmo chama por
que
AlunoContainer.getCampo(“Nome”).get()
executa
CampoAlunoNome.get(). CampoAlunoNome é uma subclasse de Campo, essa é
chamada
quando
AlunoContainer
chama
pelo
Campo
“Nome”.
Em
CampoAlunoNome.get() é realizada a lógica que obtém do banco de dados as duas
colunas, PRIMEIRO_NOME e SOBRENOME, e retorna a concatenação das duas.
Outro exemplo, a tabela Aluno possui a coluna Curso como uma variável do tipo
varchar. Porém, devido a uma necessidade de controle interno, decidiu-se criar uma tabela
com os nomes dos Cursos. Assim a coluna Curso foi substituída por uma coluna ID_CURSO
que aponta para a tabela CURSO.
51
Figura 9. Modelo Aluno alterado
O objeto Campo Curso no Repositório deve ser modificado para apontar para o ID da
tabela CURSO, porém continuando em AlunoContainer.
O objeto de negócio Aluno não foi modificado, continua obtendo o nome do Curso (que
é o que interessa para a aplicação) no objeto Campo Curso.
Outro ganho com o uso do REPOO é que na criação dos comandos SQL (consulta,
inserção, alteração e deleção) os nomes das colunas e tabelas são criadas dinamicamente, assim
a mudança dos nomes é realizada em um local, mas reflete em todas as instâncias. Dessa
forma, o desenvolvedor não tem que sair atualizando os nomes em vários arquivos ou classes
que utilizam as tabelas alteradas.
4.3 Descarga de Dados
O processo de descarga de dados neste trabalho consiste em salvar as alterações dos
dados manipulados pelos objetos de negócio no banco de dados relacional.
4.3.1 Descarregar Apenas o Necessário
Os campos do Repositório são agrupados em containers de acordo com as entidades
dos objetos (ex: Aluno, Curso, Disciplina). Cada container possui Campos (que fazem
52
a relação do registro relacional de tabela com propriedade em objeto) e CamposIDs (mesma
função de Campos porém apenas para os identificadores, definindo que determinado objeto
corresponde a determinada tabela).
Quando o Repositório inicia o processo de descarga, são selecionados os CamposIDs
(sempre) e apenas os Campos que sofreram alguma alteração. Garantindo que não sejam
trafegados na rede dados desnecessários, que não sofreram nenhuma alteração.
Os objetos Campos e CampoID trafegados pela rede chegam no Repositório do lado
do servidor de banco de dados, como visto na seção 4.1.3 Tráfego de Cargas Redundantes, este
outro Repositório por sua vez realiza as consultas no banco e possui os Campos antes das
alterações da aplicação, os objetos Campo que foram alterados substituem os antigos, dessa
forma permanecem os objetos Campo que não foram alterados junto com os que o foram.
Assim o Repositório tem os dados suficientes para realizar a atualização completa, atingindo o
melhor desempenho no banco de dados, e sem sobrecarregar a rede com dados desnecessários.
4.3.2
Descarregar Apenas Quando Necessário
O Repositório retém as informações em memória sem persistir no banco o máximo de
tempo possível. Quando ocorre o fim da transação ou quando uma consulta executada exige
que os dados estejam atualizados, o Repositório realiza o procedimento descrito no Item 3.3.1
salvando todas as informações no banco de dados.
Caso algum erro ocorra e os dados ainda não tenham sido salvos no banco, em vez de
se executar um rollback no banco de dados, apenas são descartadas as informações do
Repositório. Nenhum comando precisa ser enviado para o banco economizando assim a
conexão da rede.
Essa forma de implementação utilizando o Repositório permite que o desenvolvedor se
abstraia de controles de transações, de executar commits.
53
5 Conclusão
Esse trabalho abordou as dificuldades encontradas em persistir os dados de uma aplicação
orientada a objetos em um banco de dados relacional.
A relação entre paradigma orientado a objetos e modelo relacional é um tema discutido
constantemente. Apesar de diversas soluções importantes já terem sido criadas, o tema sempre
instiga o surgimento de novas soluções. É muito complexo criar uma solução única que atenda
a todos os pontos desse problema, assim para determinados casos algumas soluções se
sobressaem de outras.
O intuito neste trabalho foi levantar algumas dificuldades existentes e elencar soluções que
se diferenciam em algum ponto das já existentes. A base para as soluções foi a criação de uma
estrutura chamada de REPOO, utilizando conceitos clássicos e simples, como padrões de
projetos.
Este trabalho se conclui com os próximos tópicos, abordando uma análise comparativa
final das soluções aqui expostas com as existentes no mercado, e as contribuições obtidas.
5.1 Análise comparativa
Hibernate, Ibatis assim como as ferramentas ORM mais comuns, fazem uso de arquivos de
configuração em XML para realizar o mapeamento das tabelas com os objetos. Estes podem
ser simples ou bastante complexos, possibilitando realizar pequenos projetos, cujo desempenho
de acesso ao banco não é tão importante e até mesmo projetos de grande porte ou críticos, que
necessitam o máximo possível do desempenho que o SGBD permite. Os dois permitem escrita
livre em SQL para elaborar consultas complexas.
O REPOO procura alcançar um bom desempenho com a complexidade do SQL de uma
forma mais simples. A criação e utilização de logs teoricamente permite que a aplicação
54
alcance uma boa performance sozinha, decidindo o que realmente deve ser carregado e
persistido no banco, o que tende a evitar desperdício de banda de rede. Tudo sem a necessidade
de criação de muitos arquivos de configuração por parte do desenvolvedor.
Em contrapartida, alguns problemas são facilmente resolvidos com a criação de arquivos
de configuração pelas ferramentas ORM. Com o REPOO, o desenvolvedor precisa
implementar classes que realizam o mapeamento, o que pode ser mais trabalhoso, porém mais
flexível, pois permite implementações por parte do desenvolvedor, não ficando limitadas aos
recursos exclusivos da ferramenta ORM, como pode ser visto na questão do problema das
consultas polimórficas onde o Hibernate não permite utilização de uniões obrigando a
realização de diversas consultas ao banco.
Assim como no Hibernate e no Ibatis o REPOO não permite que o desenvolvedor crie
aplicações com alto desempenho em acesso a dados sem o conhecimento devido em linguagem
SQL, modelo relacional e orientação a objetos. É necessário o conhecimento para escrever
consultas eficientes.
O desacoplamento da persistência de dados dos objetos é algo não visto nos frameworks
ORM, é um ponto forte do REPOO, o desenvolvedor pode criar vários objetos com mesmo ID
em diversos pontos da aplicação, as propriedades não se multiplicam, pois estão centralizadas,
isso permite abstrair o controle de memória permitindo o desenvolvedor focar nos problemas
da aplicação.
5.2 Contribuições
As principais contribuições obtidas neste trabalho podem ser vistas nos tópicos a seguir:
- Exposição teórica de uma estrutura, o REPOO, que visa centralizar a lógica de
persistência de dados, desacoplando dos objetos da aplicação.
- Carregamento dinâmico e inteligente dos dados, diminuindo a necessidade do
desenvolvedor indicar através de arquivos de configuração quais colunas devem ser carregadas
do banco, e possibilitando o sistema se adaptar conforme seja utilizado.
- Análise das vantagens com relação ao trafego de dados na rede, em se ter uma aplicação
REPOO instalada no mesmo computador onde está o servidor de banco de dados.
55
- Análise comparativa de alguns pontos entre as soluções propostas com as soluções já
existentes, como Hibernate e Ibatis.
56
6 Trabalhos Futuros
O REPOO atualmente consiste em um conjunto de ideias com algumas implementações. A
meta é que além de sua implementação completa, este repositório se torne uma ferramenta
ORM. Algumas melhorias precisam ser feitas para conseguir esse objetivo.
- Criação de uma interface gráfica como o Ibator (on-line) facilitando a criação das classes
de mapeamento, tendo apenas que implementar fora da interface em situações pontuais ou
complexas. Dessa forma poderia ser alcançada uma produtividade (nesse aspecto) parecida
com as ferramentas de ORM atuais.
- Análise mais aprofundada das soluções aqui pesquisadas e de outras atuais no mercado.
- Implementação de concorrência, um item que por questões de simplicidade não foi
discutido no trabalho.
- A análise do paradigma de desenvolvimento por Aspecto que vem sendo mencionado no
tema de persistência de dados OO no modelo relacional, e como pode contribuir com o
REPOO.
57
7 Referências Bibliográficas
AMBLER, Scott. Mapping objects to relational databases. Disponível em: <
http://www.agiledata.org/essays/mappingObjects.html>. Acesso em: 17 nov. 2009.
APACHE
SOFTWARE
FOUNDATION.
Ibatis.
Disponível
em:<
http://ibatis.apache.org>. Acesso em: 17 nov. 2009
BEGIN,
Clinton.
Ibatis
sql
maps
tutorial.
Disponível
em:
<http://svn.apache.org/repos/asf/ibatis/java/ibatis-2/trunk/ibatis-2-docs/en/iBATISSqlMaps-2-Tutorial_en.pdf>. Acesso em: 9 set. 2009.
COCOBASE. Dynamic mapping for the enterprise version 4.0. Thought
Incorporated, 2001. Disponível em: <www.thoughtinc.com/MappingConcepts.pdf>
Acesso em: 17 nov 2009. a.
COCOBASE. Dynamic o/r mapping and dynamic transparent persistence with
CocoBase for the J2EE & J2SE Platforms. Thought Incorporated, 2002. Disponível em:
<http://www.thoughtinc.com/dynamic_transparent_persistence.html> Acesso em: 17
nov 2009. b.
COCOBASE. Thought incorporated. Disponível em: <http://www.thoughtinc.com>
Acesso em: 17 nov 2009. c.
DATE, C. J. Introdução a sistemas de banco de dados. 8. ed. Rio de Janeiro:
Campus, 2004.
58
DEITEL, H. M.. C++ como programar. 5. ed. São Paulo: Pearson Education do
Brasil, 2006.
ELMASRI, Ramez; NAVATHE, Shamkant B. Sistemas de banco de dados. 4. ed. São
Paulo: Pearson Addison Wesley, 2005.
MEDEIROS, Ernani. Desenvolvendo software com UML 2.0 definitivo. São Paulo:
Pearson Makron Books, 2006.
METSKER, Steven John. Design patterns in C#. Courier Westford: Pearson
Education, 2004.
GAMMA, Erich, et al. Padrões de projeto: soluções reutilizáveis orientado a objetos.
Trad. Luiz A. Meirelles Salgado. – Porto Alegre: Bookman, 2000.
HEUSER, Carlos Alberto. Projeto de banco de dados. 5. ed. Porto Alegre: Sagra
Luzzato, 2004. (Livros didáticos).
Core
HIBERNATE,
for
Java.
Disponível
em:
<http://docs.jboss.org/hibernate/stable/core/reference/en/pdf/hibernate_reference.pdf>.
Acesso em: 7 set. 2009.
KELLER, Wolfgang. Mapping objects to tables: a pattern language. in: proceedings
of the 2nd european conference on pattern languages of programming (europlop '97).
siemens technical report 120/SW1/FB. Munich, Germany: Siemens, 1997. Disponível
em:
<http://www.riehle.org/community-service/hillside-group/europlop-
1997/p13final.pdf>. Acesso em: 24 nov. 2009.
PINHEIRO, Jose Francisco. Um framework para persistência de objetos em banco
de dados relacionais. Niterói: Universidade Federal Fluminense, 2005. 207 f.
Dissertação (Pós Graduação em Computação) – Curso de Pós Graduação em
Computação, Universidade Federal do Rio de Janeiro, Niterói, 2005. Disponivel em:
<http://www.midiacom.uff.br/~mluiza/swLivre/zeFrancisco.pdf>. Acesso em: 1 ago
2009.
59
SILBERSCHATZ, Abraham; KORTH, Henry; SUDARSHAN, S. sistema de banco de
dados. 3. ed. São Paulo: Pearson Makron Books, 2006.
STALLINGS, William. Arquitetura e organização de computadores: projeto para o
desempenho. 5. ed. São Paulo: Prentice Hall, 2002.
VILARIM, Gilvan. Algoritmos: programação para iniciante. Rio de Janeiro: Ciência
Moderna, 2004.
60
8 Material de Leitura
ALUR, Deepak; CRUPI, John; MALKS, Dan. Core J2EE* Patterns: As melhores
Práticas e Estratégias de Design. Campus 2 ed.
JDBC API DOCUMENTATION. Sun Microsystems Inc., 2002. Disponível em:
<http://java.sun.com/j2se/1.4.2/docs/guide/jdbc/index.html> Acesso em: 7 set. 2009.
FREEMAN, Eric; FREEMAN Elizabeth. Use a cabeça!: padrões de projetos. trad.
Andreza Gonçalves. Rio de Janeiro: Alta Books Ltda, 2005.
61
Download

Padrões de Projeto em Modelagem Orientada a Objetos Persistida