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