1 Anderson Gomes da Silva 0205109 8º Semestre Mapeamento Objeto-Relacional Monografia apresentada à disciplina Trabalho de Conclusão do Curso de Ciências da Computação da Faculdade de Jaguariúna, sob. a orientação do Prof. Leonardo Hartleben Reinehr, como exigência parcial para conclusão do curso de graduação. Jaguariúna 2005 2 Silva, Anderson Gomes. Estudo Comparativo de Ferramentas de Mapeamento ObjetoRelacional, Monografia defendida e aprovada na FAJ em 15 de Dezembro de 2005 pela banca examinadora constituída pelos professores: ____________________________________________________ Profº. Leonardo Hartleben Reinehr – FAJ - Orientador ____________________________________________________ Profº. Odersom ____________________________________________________ Profº. Roberto Pacheco 3 Aos meus pais Vital e Maria do Carmo e irmãos Helinton e Erica. 4 Agradecimentos Primeiramente à Deus pois sem ele nada é possível; Aos meus pais Vital e Maria do Carmo, por toda educação e amor que foram investidos em mim durante a minha vida; Aos meus irmão e minha namorada que me incentivaram neste trabalho; Aos meus professores, pelas conversas, conselhos e ensinamento que servirão para a vida toda; Ao meu orientador Leonardo Hartleben Reinehr, pela confiança e apoio depositados em mim; À todos meus amigos: Leonardo, Michel, Robson, Juliano, pela amizade e apoio, algo que vai durar muito mais do que quatro anos; À todas as pessoas que me ajudaram nesta etapa da minha vida; 5 "É muito melhor arriscar coisas grandiosas, alcançar triunfos e glórias, mesmo expondo-se a derrota, do que formar fila com os pobres de espírito que nem gozam muito nem sofrem muito, porque vivem nessa penumbra cinzenta que não conhece vitória nem derrota." (Theodore Roosevelt) 6 SILVA, Anderson Gomes. Estudo Comparativo de Ferramentas de Mapeamento Objeto-Relacional. 2005. Monografia. (Bacharelado em Ciências da Computação) – Curso de Ciências da Computação da Faculdade de Jaguariúna, Jaguariúna. RESUMO Este trabalho é um estudo comparativo entre as Ferramentas de Mapeamento Objeto-relacional, com enfoque ao Banco de Dados Objeto-Relacional. E tem como objetivo avaliar o estado da arte da teoria de mapeamento objeto-relacional, identificando as características e necessidades desse mecanismo, ele também ira mostrar os principais frameworks para mapeamento objeto-relacional, identificando as vantagens de sua utilização, funcionalidades oferecidas e características de implementação de acordo com a teoria de mapeamento objeto-relacional. Mostrara também a implementação de um estudo de caso utilizando os frameworks estudados, comparando os resultados obtidos em termos de funcionalidades, performance, flexibilidade e facilidade de uso entre outros aspectos. Palavras-chave: Banco de Dados Relacional, Ferramentas de Mapeamento ObjetoRelacional. ABSTRACT This work is a comparative study between the Relational-object Mapping Tools, with approach to the Database Relational-object. Its target is to evaluate the art state of the theory of mapping relational-object, and identify the characteristics and needs of this mechanism, it will also show the main frameworks to mapping relational-object, to identify the advantages of its use, offered functionalities and implementation characteristics according to the theory of mapping relational-object. It will also show the implementation of a case-study using the analyzed frameworks, comparing the acquired results in functionalities terms, performance, flexibility and facilities, and others aspects. Key-Word: Relationary data base, Tools of Objeto-Relacional Mapping. 7 LISTA DE ABREVIATURAS E SIGLAS API CASE OID OO OO-ER UML XMI XML OQL SGBD SGBDOO SGBDOR SGBDR SQL ER Application Programming Interface Computer Aided Software Engineering Object Identification Orientado a Objeto Orientado a Objeto Entidade Relacional Unified Modeling Language XML Metadata Interchange eXtensible Markup Language Object Query Language Sistema Gerenciador de Banco de Dados Sistema Gerenciador de Banco de Dados Orientado a Objeto Sistema Gerenciador de Banco de Dados Objeto Relacional Sistema Gerenciador de Banco de Dados Relacionais Structured Query Language Entidade Relacionamento 8 LISTA DE FIGURAS FIGURA 1 FIGURA 2 FIGURA 3 FIGURA 4 FIGURA 5 FIGURA 6 FIGURA 7 FIGURA 8 FIGURA 9 Mapeamento Básico Mapeamento de Relacionamento (um-para-um) Mapeamento de Relacionamento (um-para-muitos) Mapeamento de Relacionamento (muitos-para-muitos) Uma Tabela para toda Hierarquia Uma Tabela por Classe Concreta Uma Tabela por Classe Uma Visão Simplista do Hibernate Componentes do Hiberante 9 LISTAS DE TABELAS TABELA 1 TABELA 2 TABELA 3 TABELA 4 TABELA 5 TABELA 6 TABELA 7 TABELA 8 TABELA 9 TABELA 10 Comparativo entre técnicas de mapeamento de classe Parâmetros principais para configuração da conexão JDBC Parâmetros que definem comportamentos em tempo de execução Criando um SessionFactory através do objeto configuration Classe candidata a persistência Implementação da classe pessoa Declaração de criação da tabela que armazena a classe pessoa Mapeando a classe pessoa numa tabela Tipos de geradores presentes no Hiberante Mapeamento da classe pessoa com parâmetros opcionais TABELA 11 Associação entre os tipos do Hibernate, classes Wrapper Java e tipo no BD TABELA 12 TABELA 13 TABELA 14 TABELA 15 Mapeamento conteplado no SessionFactory Banco de dados suportados pelo Hibernate Comparações do Hibernate com outros frameworks de persistências Diagrama de Classe “Biblioteca Música” 10 SUMÁRIO LISTA DE ABREVIATURA E SIGLAS......................................................................7 LISTAS DE FIGURAS..................................................................................................8 LISTAS DE TABELAS.................................................................................................9 1. INTRODUÇÃO………………...............................................................................11 2. REVISÃO BIBLIOGRÁFICA……………………................................................21 3. MAPEAMENTO OBJETO RELACIONAL……………...................................... 39 4. FERRAMENTAS DE MAPEAMENTO OBJETO RELACIONAL………..........59 5. ESTUDO DE CASO…………………...................................................................66 6. RESULTADOS OBTIDOS.....................................................................................68 7. CONCLUSÕES.......................................................................................................70 8. REFERÊNCIA BIBLIOGRÁFICA.........................................................................71 11 1. INTRODUÇAO Desde seu desenvolvimento até os dias atuais, bancos de dados relacionais sempre foram os mais utilizados no cenário comercial [DATE, SILBERSCHATZ]. Por outro lado, nos últimos anos houve uma crescente disseminação das linguagens orientadas a objeto no desenvolvimento de aplicações. Dessa forma, hoje existe um grande número de aplicações orientadas a objeto que acessam bancos de dados relacionais. Existe uma notada incompatibilidade entre o modelo orientado a objetos e o modelo relacional [AGILE DATA], a qual dificulta a transformação dos dados armazenados em um modelo para o outro. Por isso, é importante a existência de mecanismos para realizar o mapeamento das classes do modelo orientado a objeto para tabelas do modelo relacional. A teoria de mapeamento objeto-relacional define um conjunto de características necessárias a tais mecanismos, além de apresentar possíveis soluções para os problemas de incompatibilidade entre os modelos [DATE, SILBERSCHATZ, AGILE DATA]. Os frameworks de mapeamento objeto-relacional oferecem enormes facilidades para a realização desse tipo de mapeamento, implementando as soluções indicadas na teoria de mapeamentos e permitindo que as aplicações executem mapeamentos por meio de configuração e definição de regras ao invés da escrita de linhas de código [Hibernate]. O presente trabalho tem por objetivo mostrar as principais características de um Mapeamento Objeto-Relacional, com enfoque em um estudo comparativo de ferramentas de mapeamento e na identificação de problemas e questões em aberto das ferramentas existentes. Para isso, será implementado um estudo de caso usando diversas tecnologias e frameworks existentes, comparando os resultados obtidos. 12 2. REVISÃO BIBLIOGRÁFICA 2.1. BANCO DE DADOS RELACIONAIS Um banco de dados é um conjunto de informações com uma estrutura regular. Um banco de dados é normalmente, mas não necessariamente, armazenado em algum formato de máquina lido pelo computador. Há uma grande variedade de banco de dados, desde simples tabelas armazenadas em um único arquivo até gigantescos bancos de dados com muitos milhões de registros, armazenados em salas cheias de dados rígidos. Os banco de dados caracteristicamente moderno são desenvolvidos desde os anos da década de 1960, um dos pioneiros neste trabalho foi Charles Bachman. Existe uma grande variedade de banco de dados, desde exemplos simples como uma simples coleção de tabelas até um modelo teoricamente definido, o relacional [AGILE DATA]. O modelo de dados lógico relacional é atualmente o mais utilizado nos SGBDs comerciais. Entretanto, este modelo possui um sistema de tipos simples e restrito, o que dificulta a descrição de algumas aplicações atuais que necessitam tipos mais complexos e características do modelo Orientado a Objetos. Sistemas de Banco de Dados que utilizam o modelo relacional, ou seja, SGBDRs, são também considerados sistemas de segunda geração de SGBDs visto que os sistemas de Banco de Dados Hierárquicos e de Rede são considerados a primeira geração. Assim, os sistemas de Banco de Dados Objeto Relacionais são classificados como a terceira geração de SGBDs. 2.2. ORIENTAÇÃO A OBJETO Orientação a objeto pode ser definida como um conjunto de disciplinas de modelagem de software que facilitam a construção sistemas complexos a partir de componentes individuais). O apelo intuitivo da orientação a objeto é que ele proporciona conceitos e ferramentas para modelar e representar o mundo real. As vantagens da orientação a objeto na programação e na modelagem de dados são muitas[AGILE DATA]. 13 A programação de objeto (orientada) permite uma representação mais direta do modelo do mundo real no código. O resultado é que a transformação radical das requisições do sistema (definido em termos de usuários) para especificação do sistema (definido em termos de computador) é enormemente reduzida. 2.3. IMPEDÂNCIA Os desenvolvedores de aplicações de bancos de dados (ou seja, qualquer aplicação que acesse dados armazenados em um banco de dados) freqüentemente se vêem brigando com problemas de diferenças de impedância: a inerente falta de casamento entre os modelos de dados relacionais e os orientados a objeto. Os esforços para “mapear” dados relacionais em um formato utilizável de objetos frequentemente prejudicam tanto a produtividade do programador quanto o desempenho da aplicação. A diferença de impedância é uma expressão utilizada em engenharia elétrica, mas no contexto deste trabalho, refere-se à diferença que existe entre os modelos de dados relacional e objeto. O modelo relacional organiza todos os dados em linhas e colunas, onde cada linha representa um registro. Se os dados forem por demais complexos para serem representados em forma de tabela, tabelas adicionais são cridas para conter as informações “relacionadas”. Dessa forma, cada tabela em um esquema relacional conterá registro mas não todos os dados para uma grande quantidade de registros. O modelo de dados orientado a objeto não está limitado a manter as informações em linhas e colunas. Em vez disso, o desenvolvedor cria uma definição, um modelo, que descreve completamente uma determinada classe de informações. Cada registro (objeto) é uma instância específica daquela classe. Assim, cada registro contém todos os itens de informação para um, e apenas um, registro. Mas isso não é tudo, as definições de classes também podem incluir trechos de programação, denominados métodos que apenas sobre os dados descritos pela classe. Não há uma concepção análoga no modelo relacional. 2.3.1. Usando um banco de dados relacional 14 Esta seção já discute como a tentativa de usar um banco de dados relacional com uma aplicação baseada em tecnologia de objetos apresenta sérios problemas de diferença de impedância. Mas as vezes os desenvolvedores não tem escolha. Pode ser que eles tenham de acessar dados que residem em um banco de dados relacional. Nesse caso, uma opção é usar ema ferramenta de “mapeamento objeto-relacional”, quer seja ela autônoma, quer consiste em facilidades disponível nos bancos de dados “objeto-relacional”. Essencialmente, as ferramentas de mapeamento criam um arquivo (um mapa) que contém as regras para a tradução entre objetos e tabelas relacionais. Os desenvolvedores devem especificar exatamente como a tradução será feita, ou seja, que propriedades do objeto correspondem a quais colunas de que tabelas e vice-versa. Uma vez criado, o mapa é salvo e invocado sempre que uma aplicação move os dados para o banco de dados. Algumas ferramentas de mapeamento objeto relacional provêem um componente de cachê em tempo de execução para ajudar a compensar a perda de desempenho causada pela tradução entre as formas relacionais e de objetos. Além de poder causar problema de performance durante a execução, o mapeamento objeto-relacional pode atrasar significativamente o desenvolvimento da aplicação. A maioria das ferramentas de mapeamento não implementa conceitos de modelagem de objetos, como herança e polimorfismo, ou o faz apenas parcialmente. Assim, à medida que uma aplicação é adaptada e modificada, mapas objeto-relacional novos e atualizados têm de ser criados. Os desenvolvedores que enfrentam o problema da diferença de impedância entre aplicação orientadas a objeto e bancos de dados relacionais relacional podem querer considerar a opção de migrar os dados para um sistema de armazenamento mais amigável. Eles devem então avaliar, o esforço de reformatar e transferir seus dados uma só vez, em relação ao trabalho constante e as perdas de desempenho que resultam do uso de um mapa objeto-relacional. 2.3.2. Usando um banco de dados de objeto À primeira vista, parecia que a diferença de impedância poderia ser totalmente eliminada armazenando-se os dados em um banco “puramente” de objetos. Isso é 15 parcialmente verdade. Em geral, para uma aplicação orientada a objeto é fácil interagir com um banco de dados orientado a objeto. No entanto, neste cenário, a diferença de impedância ocorre quando se quer executar uma consulta SQL a essa base de dados. A SQL é, de longe a linguagem de consulta mais amplamente utilizada em todo o mundo, e ela assume que os dados estão armazenados em tabelas do modelo relacional. Alguns fabricantes de banco de dados orientados a objeto fornecem o acesso a dados via linguagem de consulta de objeto (OQL, do inglês Object Query Language), mas essas linguagens não têm aceitação generalizada. Para ser compatível com as aplicações comuns de analise de dados e de geração de relatórios, um banco de dados orientado a objeto deve prover algum mecanismo para representar os dados como tabelas relacionais. A solução típica é mais uma vez o mapeamento. Os pontos negativos do mapeamento (perdas de performance de dados) ainda se aplicam ao caso. O aspecto positivo é que o mapeamento só precisa ser chamado quando uma consulta SQL é feita á base de dados. 2.4. CAMADA DE PERSISTÊNCIA Podemos definir persistência de dados como uma forma de manter a existência da informação mesmo fora da aplicação. Podemos persistir a informação em um banco de dados, em um arquivo de dados ou qualquer outro meio existente e o fato da informação existir também fora da aplicação faz com que essas informações possam ser compartilhadas por outras aplicações. Para permitir um processo de mapeamento entre sistemas baseados em objetos e bases de dados relacionais, foram propostas diversas idéias que convergiram para o conceito de Camada de Persistência. Conceitualmente, uma Camada de Persistência de Objetos é uma biblioteca que permite a realização do processo de persistência (isto é, o armazenamento e manutenção do estado de objetos em algum meio não-volátil, como um banco de dados) de forma transparente. Graças à independência entre a camada de persistência e o repositório de dados utilizado, também é possível gerenciar a persistência de um modelo de objetos em diversos tipos de repositórios, com pouco ou nenhum esforço extra. A utilização deste 16 conceito permite ao desenvolvedor trabalhar como se estivesse em um sistema completamente orientado a objetos – utilizando métodos para incluir, alterar e remover objetos e uma linguagem de consulta para SGBDs Orientados a Objetos – comumente a linguagem OQL – para realizar consultas que retornam coleções de objetos instanciados. 2.4.1. Vantagens da utilização As vantagens decorrentes do uso de uma Camada de Persistência no desenvolvimento de aplicações são evidentes: a sua utilização isola os acessos realizados diretamente ao banco de dados na aplicação, bem como centraliza os processos de construção de consultas e operações de manipulação de dados (insert, update e delete) em uma camada de objetos inacessível ao programador. Este encapsula mento de responsabilidades garante maior confiabilidade às aplicações e permite que, em alguns casos, o próprio SGBD ou a estrutura de suas tabelas possam ser modificados, sem trazer impacto à aplicação nem forçar a revisão e recompilação de códigos. 2.4.2. Requisitos de uma camada de persistência Segundo Scott Ambler, pesquisador e autor de diversos livros, uma Camada de Persistência real deve implementar as seguintes características: • Dar suporte a diversos tipos de mecanismos de persistência: um mecanismo de persistência pode ser definido como a estrutura que armazenará os dados – seja ela um SGBD relacional, um arquivo XML ou um SGBD OO, por exemplo. Uma Camada de Persistência deve suportar a substituição deste mecanismo livremente e permitir a gravação de estado de objetos em qualquer um destes meios. • Encapsula mento completo da camada de dados: o usuário do sistema de persistência de dados deve utilizar-se, no máximo, de mensagens de alto nível como save ou delete para lidar com a persistência dos objetos, deixando o tratamento destas mensagens para a camada de persistência em si. • Ações com multi-objetos: Suportar listas de objetos sendo instanciadas e retornadas da base de dados deve ser um item comum para qualquer implementação, tendo em vista a freqüência desta situação. 17 • Transações: ao utilizar-se da Camada de Persistência, o programador deve ser capaz de controlar o fluxo da transação – ou ter garantias sobre o mesmo, caso a própria Camada de Persistência preste este controle. • Extensibilidade: A Camada de Persistência deve permitir a adição de novas classes ao esquema e a modificação fácil do mecanismo de persistência. • Identificadores de Objetos: A implementação de algoritmos de geração de chaves de identificação garante que a aplicação trabalhará com objetos com identidade única e sincronizada entre o banco de dados e a aplicação. • Cursores e Proxies: As implementações de serviços de persistência devem ter ciência de que, em muitos casos, os objetos armazenados são muito grandes – e recuperá-los por completo a cada consulta não é uma boa idéia. Técnicas como o lazy loading (carregamento tardio) utilizam-se dos proxies para garantir que atributos só serão carregados à medida que forem importantes para o cliente e do conceito de cursores para manter registro da posição dos objetos no banco de dados (e em suas tabelas específicas). • Registros: Apesar da idéia de trabalhar-se apenas com objetos, as camadas de persistência devem, no geral, dispor de um mecanismo de recuperação de registros conjuntos de colunas não encapsuladas na forma de objetos, como resultado de suas consultas. Isto permite integrar as camadas de persistências a mecanismos de geração de relatórios que não trabalham com objetos, por exemplo, além de permitir a recuperação de atributos de diversos objetos relacionados com uma só consulta. • Arquiteturas Múltiplas: O suporte a ambientes de programas stand-alone, cenários onde o banco de dados encontra-se em um servidor central e mesmo arquiteturas mais complexas (em várias camadas) deve ser inerente à Camada de Persistência, já que a mesma deve visar a reusabilidade e fácil adaptação a arquiteturas distintas. • Diversas versões de banco de dados e fabricantes: a Camada de Persistência deve tratar de reconhecer diferenças de recursos, sintaxe e outras minúcias existentes no acesso aos bancos de dados suportados, isolando isto do usuário do mecanismo e garantindo portabilidade entre plataformas. 18 • Múltiplas conexões: Um gerenciamento de conexões (usualmente utilizando-se de pooling) é uma técnica que garante que vários usuários utilizarão o sistema simultaneamente sem quedas de performance. • Queries SQL: Apesar do poder trazido pela abstração em objetos, este mecanismo não é funcional em cem porcento dos casos. Para os casos extremos, a Camada de Persistência deve prover um mecanismo de queries que permita o acesso direto aos dados – ou então algum tipo de linguagem de consulta similar à SQL, de forma a permitir consultas com um grau de complexidade maior que o comum. • Controle de Concorrência: Acesso concorrente a dados pode levar a inconsistências. Para prever e evitar problemas decorrentes do acesso simultâneo, a Camada de Persistência deve prover algum tipo de mecanismo de controle de acesso. Este controle geralmente é feito utilizando-se dois níveis – com o travamento pessimístico (pessimistic locking), as linhas no banco de dados relativas ao objeto acessado por um usuário são travadas e torna-se inacessíveis a outros usuários até o mesmo liberar o objeto. No mecanismo otimístico (optimistic locking), toda a edição é feita em memória, permitindo que outros usuários venham a modificar o objeto. 2.4.3. Camadas de persistência e linguagens de programação Diversas implementações de camadas de persistência estão disponíveis gratuitamente na Internet. Estas bibliotecas muitas vezes tratam da geração dos esquemas de dados (mapeamentos) automaticamente e podem até mesmo efetuar uma engenharia reversa – criando hierarquia de classes a partir de um esquema de tabelas em banco de dados. As Camadas de Persistência que geralmente trabalham com apenas um esquema de mapeamento de classes para tabelas, diversas estratégias de geração de identificadores, suporte a quaisquer tipos de relacionamento e geração de código SQL automatizada. Na linguagem Java, podemos citar algumas destas bibliotecas de persistência: • Hibernate – uma implementação que permite a persistência transparente de objetos em bases de dados utilizando JDBC e o mapeamento de classes para XML. Trata-se de um serviço de persistência e recuperação de objetos, já que, ao contrário dos 19 frameworks de persistência, não é necessário estender nenhuma classe especial para que um objeto possa ser armazenado. Projetado para permitir integração com ambientes J2EE, o Hibernate utiliza reflexão (reflection) para tratar a persistência, gerando código SQL à medida que for necessário. Atualmente compatível com 11 SGBDs comerciais em sua versão 1.1 (Oracle, DB2, MySQL, PostgreSQL, Sybase, SAP DB, HypersonicSQL, Microsoft SQL Server, Progress, Mckoi SQL, Pointbase e Interbase), o Hibernate é distribuído segundo a licença LGPL e suporta uma API baseada no padrão ODMG 3.0 (o padrão para construção de SGBDs Orientados a Objetos). Dentre outros recursos interessantes, o Hibernate suporta gerenciamento remoto utilizando-se a API JMX e é capaz de gerar esquemas de dados (tabelas) para representar hierarquias de classes. • Castor – um framework de ligação de dados (databinding), o Castor propõe-se a ser "a menor distância entre objetos Java, documentos XML, diretórios LDAP e dados SQL",promovendo mapeamentos entre todas estas estruturas de representação de objetos. A API do pacote Castor específica para a persistência em bancos de dados relacionais é a JDO – uma implementação inspirada no padrão Java Data Objects da Sun. A API provê integração com ambientes J2EE. Atualmente em sua versão 0.9, o Castor suporta os SGBDs Oracle, Sybase, SQL Server, DB2, Informix, PostgreSQL, Hypersonic SQL, InstantDB, Interbase, MySQL e SAP DB. A distribuição segue a licença LGPL. • Object-Relational Java Bridge (OJB) - um projeto do grupo Apache para prover uma implementação open-source dos padrões de mapeamento de objetos ODMG e JDO, o OJB permite que objetos sejam manipulados sem a necessidade de implementar nenhuma interface em especial ou estender alguma classe específica. A biblioteca dá suporte a cenários cliente-servidor (aplicações distribuídas) ou standalone, de forma que é possível utilizar a API OJB para persistência de objetos. Além disso, a biblioteca possui integração com o sistema de geração de logs. Em sua versão 0.9, o OJB dá suporte a configuração de esquemas em tempo de execução, geração de tabelas para mapear uma hierarquia de classes ou classes relativas a um conjunto de tabelas e implementa uma série de elementos que visam melhorar a performance da Camada de Persistência. Os SGBDs suportados pela 20 implementação atual incluem DB2, Hypersonic SQL, Informix, MS-Access, MSSQL Server, MySQL, Oracle, PostgreSQL, Sybase e SAP DB. A distribuição é feita segundo a licença Apache. • Torque – um framework de persistência desenvolvido como subprojeto do projeto Apache Turbine, a API trabalha gerando toda a estrutura de banco de dados, classes e código SQL para acesso aos dados relativos a um esquema pré-configurado. O esquema é escrito na forma de um arquivo XML, que é interpretado pela biblioteca utilizando o Ant, uma ferramenta de compilação de código (build tool) do projeto Apache. A API torque encontra-se em sua versão 3.0 e é distribuída segundo a licença Apache. 2.5. O QUE SÃO FRAMEWORKS Um framework OO é uma estrutura de classes inter-relacionadas que constitui uma implementação inacabada, para um conjunto de aplicações de um domínio, além de ser uma técnica que faz o reuso do projeto. O termo framework que inicialmente estava associado ao conceito de bibliotecas de classes reutilizáveis, mais recentemente, teve seu conceito estendido para qualquer solução incompleta que pode ser completada através da instanciação, possibilitando a criação de mais uma aplicação dentro do domínio-alvo do framework (Esta definição tem similaridades com a do gerador de artefatos). Atualmente esta técnica tem sido muito apoiada e utilizada pela comunidade de desenvolvimento de SI. Há uma vasta gama de frameworks disponíveis, tanto na forma de software livre quanto proprietário, alguns mais restritos, outros mais flexíveis. É necessário conhecer bem um framework antes de adotá-lo em seu projeto, muitos ainda são muito imaturos e podem condenar o software a um curto período de sucesso. 21 3. MAPEAMNTO OBJETO RELACIONAL O termo Mapeamento Objeto Relacional refere-se a técnica de mapear os registro do Banco de Dados em objetos e persistir as informações contidas nos objeto em forma de linhas e colunas. Como o próprio nome diz, Mapeamento Objeto / Relacional,é responsável por mapear classes e atributos do modelo orientado a objeto para tabelas e colunas do banco de dados. Existem várias formas de fazer esse mapeamento. Alguns frameworks utilizam a linguagem XML, outros nos obrigam a implementar alguma Interface ou trabalhar com os Atributos do .NET, mas o objetivo é sempre o mesmo: Permitir que o framework consiga gerar os comandos SQL dinamicamente. Uma outra característica deste modelo é a independência do banco de dados. Devido a geração de comandos dinâmicos, o framework pode analisar qual banco de dados a aplicação está acessando e gerar os comandos no dialeto específico do banco de dados, ou seja, é possível mudar o banco de dados da aplicação apenas alterando um arquivo de configuração. 3.1. Mapeando objetos para tabelas Para permitir a correta persistência de objetos em um banco de dados relacional, algum acordo deve ser feito no tocante à forma como os dados serão armazenados. Existem diversas técnicas que permitem o mapeamento de conjuntos de objetos, cada qual com suas vantagens e desvantagens sobre as demais. Em geral, uma Camada de Persistência implementa uma destas técnicas, de forma que o desenvolvedor de software, ao escolher o mecanismo de persistência com o qual trabalhará, sabe como deve organizar as tabelas em seu banco de dados para suportar o esquema de objetos desejado. No decorrer deste artigo, detalhamos como é feito o mapeamento de cada um dos elementos de um objeto: seus atributos, relacionamentos e classes descendentes (herança). 22 3.2. Mapeando atributos Ao transpor-se um objeto para uma tabela relacional, os atributos do mesmo são mapeados em colunas da tabela. Este processo de mapeamento deve levar em consideração fatores como a tipagem dos dados (alguns SGBDs podem não suportar tipos binários longos, por exemplo) e o comprimento máximo dos campos (no caso de números e strings). Também é importante lembrar que, em diversos casos, atributos de um objeto não devem ter obrigatoriamente uma coluna em uma tabela que os referencie. Como exemplo, podemos citar o valor total de um pedido: este dado poderia ser armazenado no objeto para fins de consulta, mas mantê-lo no banco de dados talvez não seja uma idéia tão interessante, por tratar-se de um valor que pode ser obtido através de consultas. Além disso, existem casos onde um atributo pode ser mapeado para diversas colunas (exemplos incluem endereços completos, nome dividido em 'primeiro nome' e 'sobrenome' no banco de dados) ou vários atributos podem ser mapeados para uma mesma coluna (prefixo e número de telefone, por exemplo). As implementações de Camadas de Persistência provêem, em alguns casos, suporte a este tipo de situação. 3.3. Mapeamento de classes em tabelas O mapeamento de estruturas de classes em tabelas de uma base de dados relacional nem sempre é um processo simples: enquanto alguns acham interessante a adoção de "tabelões" (isto é, tabelas não-normalizadas agrupando dados de diversas entidades) como repositório para os dados, outros preferem ater-se às regras propostas pelas teorias de normalização de bancos de dados relacionais. As três técnicas de mapeamento de objetos mais comumente implementadas (inclusive em Camadas de Persistência) são detalhadas a seguir. É comum a adoção de uma destas técnicas, mesmo quando nenhum tipo de mecanismo de persistência automático é adotado no desenvolvimento. 3.4. Mapeamento de uma tabela por hierarquia 23 Segundo esta estratégia, toda a hierarquia de classes deve ser representada por uma mesma tabela no banco de dados: uma coluna que identifique o tipo do objeto serve para identificar a classe do objeto representado por cada linha na tabela, quando nenhum outro modo de identificação é viável. As desvantagens desta estratégia são evidentes: a ausência de normalização dos dados fere as regras comuns da teoria de modelagem de dados – além disso, para hierarquias de classes com muitas especializações, a proliferação de campos com valores nulos na maioria das linhas da tabela se torna também um problema potencial. 3.5. Mapeamento de uma tabela por classe concreta Nesta estratégia, teremos uma tabela no banco de dados para cada classe concreta presente em nosso sistema. A tabela identifica a classe de todos os elementos contidos na mesma, tornando desnecessário o mecanismo de Object Type adotado na estratégia anterior. A estratégia de geração de uma tabela para cada classe concreta leva à redundância de dados: quaisquer atributos definidos em uma superclasse abstrata na hierarquia devem ser criados em todas as tabelas que representam subclasses da mesma. Além disso, mudar o tipo (especializar ou generalizar) um objeto torna-se um problema, já que é necessário transferir todos os seus dados de uma tabela para outra no ato da atualização. 3.6. Mapeamento de uma tabela por classe Na terceira estratégia proposta, criamos uma tabela para cada classe da hierarquia, relacionadas através do mecanismo de especialização padrão do banco de dados (utilização de chaves estrangeiras). Segundo esta modalidade de mapeamento, tenta-se ao máximo manter a normalização de dados, de forma que a estrutura final das tabelas fica bastante parecida com a hierarquia das classes representada pela UML. A colocação de um identificador de tipo (Object Type) na classe-pai da hierarquia permite identificar o tipo de um objeto armazenado nas tabelas do sistema sem forçar junções entre as tabelas, garantindo melhorias na performance, e é uma estratégia comumente utilizada. Esta é a técnica que mais naturalmente mapeia objetos para bancos de dados relacionais, de forma 24 que as Camadas de Persistência geralmente forçam a utilização de um esquema de dados que siga esta modalidade de mapeamento. A quantidade de junções (joins) entre tabelas para obter todos os dados de um objeto o seu principal ponto negativo. A tabela 1 faz um comparativo destas três técnicas quanto à facilidade de consulta a dados interativa (ad-hoc reporting), facilidade implementação, facilidade de acesso aos dados, acoplamento dos dados das classes mapeadas, velocidade de acesso e suporte a polimorfismo. Uma tabela por hierarquia de classes Uma tabela por classe concreta Uma tabela por classe Ad-hoc reporting Simples Médio Médio/Difícil Facilidade de implementação Simples Médio Difícil Facilidade de acesso Simples Simples Médio/Simples Acoplamento Alto Baixo Velocidade de acesso Rápido Rápido Médio/Rápido Suporte a polimorfismos Baixo Alto Muito alto Médio Tabela 1. Comparativo entre técnicas de mapeamento de classes 3.7. Mapeamento de relacionamentos Os relacionamentos de associação entre objetos são uma das características mais facilmente mapeadas. Conceitualmente, existem apenas trás tipos de relacionamentos possíveis (um-para-um, um-para-muitos e muitos-para-muitos). Relacionamentos um-para-um necessitam que uma chave (foreign key) seja posta em uma das duas tabelas, relacionando o elemento associado na outra tabela. Dependendo da disposição desta chave estrangeira, podemos definir a navegabilidade do relacionamento (que se dá sempre da tabela que possui a chave estrangeira para a tabela referenciada). Para manter relacionamentos um-para-muitos, adota-se a mesma técnica: uma referência na forma de chave estrangeira deve ser posta na tabela que contém os objetos múltiplos (lado 25 "n" do relacionamento).No caso de relacionamentos muitos-para-muitos (ou n-para-n), convenciona-se criar uma tabela intermediária que armazene pares de chaves, identificando os dois lados do relacionamento. Há uma tendência para a utilização de Linguagens de Programação Orientadas a Objeto(LPOO) e mecanismos de persistência diversos, principalmente, Banco de Dados Relacionais(BDR).Surge então um problema, a integração entre a linguagem e o BD. Embora existam várias APIs e modelos de mapeamento que possibilitam esta integração, elas devem ser utilizadas de acordo com diretrizes para que não se perca os benefícios da orientação a objetos e nem do BDR. O simples mapeamento das classes, em nível de projeto, para tabelas do BDR não garante a resolução do problema, na verdade existem outros aspectos, não menos importantes, que podem levar a, violação dos princípios básicos da orientação a objetos como encapsula-mento e modularização, ou descaracterização da arquitetura adotada. Mesmo assim o modelo de objetos do banco é diferente do modelo de objetos utilizado pela linguagem de programação. Enquanto a linguagem trabalha com objetos na memória, o banco trabalha com objetos em disco, o que exige algoritmos e estratégias diferenciadas. Além de que, os BDOOs não são, atualmente, a tecnologia padrão no mercado, por conta do legado em investimento em sistemas desenvolvidos, pela cultura dos profissionais atuando no mercado ou até mesmo por questões de performance. Algumas ferramentas RAD, como DelphiTM, JbuilderTM e Dreamweaver, tornam semi-automática a integração de uma LPOO com um BDR. No entanto essa implementação é realizada sem a preocupação de critérios que garantam a continuidade e reversibilidade da implementação em relação ao projeto. Estes erros não são somente cometidos nestas condições, existem diversas “implementações ad hoc” que infringem estes e outros aspectos. Um mecanismo de persistência tem três componentes básicos, são eles: – Regras – API de mapeamento; de acesso ao Banco de Dados; – Linguagem de consulta; 26 Este componentes se interligam para gerar o mecanismo de persistência, que deve ter como objetivos a maior abstração possível do BDR nas regras de negócio e a melhor performance possível. Além disso um mecanismo deve considerar também as funcionalidades tanto da LPOO quanto do BDR, resultando maior reusabilidade, extensibilidade e eficiência. No caso de se obter reusabilidade nas regras de negócio orientado a objeto é preciso seguir o princípio da independência dos objetos de negócio em relação ao mecanismo de persistência. As regras de mapeamento gerenciam como o modelo OO que é mais rico semanticamente, vai ser mapeado para o modelo relacional. Como irão se comportar herança, agregação, entre outros devem estar definidos nestas regras. A linguagem de consulta é responsável para manipular os dados do banco, pode ser baseada em objetos ou não. As APIs são responsáveis pela integração do mecanismo com as regras de negócio. Estas podem ser intrusivas ou não. Quando estas impõem regras sob criação das classes persistentes, a API é intrusiva, caso contrário não. A tendência é que as APIs sejam não intrusivas, porém dificilmente o BD será utilizado com eficiência, bem como os conceitos transparentes ao Banco de Dados, como transações, serão mais difíceis de implementar sem modificar ou denegrir responsabilidades no modelo de objetos. Em termos de performance deve-se desenvolver uma política sobre como e quais os atributos serão carregados, em uma consulta. Podemos utilizar o carregamento antecipado, ou o carregamento tardio4. Em alguns casos deve-se ter uma política de escrita no BD também. 3.2. MAPEAMENTO OO – ER A integração objeto-relacional requer uma estratégia para mapeamento de modelo objeto para o modelo relacional. O banco de dados relacional possui características importantes tais como consultas rápidas, compartilhamento de informações entre programas e armazenamento permanente. O modelo objeto possui componentes, estado e é baseado 27 em estruturas que combinam código e dados e ainda possuem sua própria identidade (Object Identification – OID) Esta identidade não depende dos valores que o objeto possui, ela possibilita estabelecer referências entre objetos e definir os relacionamentos, os quais podem ser dos seguintes tipos: associação, agregação, generalização/especialização. OIDs possibilitam simplificar a estratégia de uso de chaves no banco de dados, eles facilitam a navegação entre objetos simplificados os joins. Outra vantagem é que o uso de OIDs facilitam a manutenção dos relacionamentos entre objetos. Quando todas as tabelas possuem suas chaves baseadas num mesmo tipo de colunas, torna-se mais fácil escrever o código e tirar vantagens disso. O modelo objeto e o modelo relacional são fundamentalmente diferentes, o modelo objeto é útil para expressar relações complexas entre objetos nas Linguagens de Programação Orientada a Objeto como, C++ e Java. Já o modelo relacional é útil para gerenciar grande volume de dados em Banco de Dados Relacional como, SQL Server e Oracle. Sendo assim cada uma das classes do modelo OO é transformada em uma tabela no modelo relacional. Para as associações, o mapeamento é feito ou com a criação de novas tabelas ou com a cópia das chaves de uma tabela para outra. Para agregação, a regra é generalização /especialização, cuja transformação para o modelo relacional é executado com interação do usuário, uma vez que para um mesmo caso pode haver diferentes formas de transformação. Existem algumas regras de transformação que tem por finalidade a conversão do modelo OO para o modelo relacional. As regras se dividem em (OBJECTMATTER, 2003): - Mapeamento Básico - Mapeamento Herança de Classe - Mapeamento Relacionamento de Objeto Serão descritas as técnicas fundamentais requeridas para o sucesso do mapeamento objeto para o relacional. Isto poderá ajudar a rever os conteúdos que predominam no desenvolvimento e praticas que envolvem este assunto 3.2.1. Mapeamento Básico 28 A classe pode ser mapeada para tabelas. O simples mapeamento entre a classe persiste e a tabela é um-para-um. Neste caso, todos os atributos da classe persistente são representados por todas as colunas da tabela. Cada instância da classe do negócio é armazenada em uma linha da tabela. Um atributo de uma classe pode ser mapeado para zero ou mais colunas. È importante lembrar que nem todos os atributos são persistentes, por exemplo, um atributo total que é usado para instanciar um somatório, logo, este não é persistido no banco de dados. Alguns atributos dos objetos são objetos por si só, como exemplo: endereço, cep, rua,etc.. portanto devem ser tratados como relacionamentos. 3.2.2. Mapeamento herança de classe O conceito de herança lança vários interesses do entrelaçamento de salvar objetos em banco de dados relacionais. Este assunto basicamente concentra-se em imaginar como organizar o atributo herdado em seu modelo persistente. A maneira como será resolvido este desafio poderá ter um grande impacto no projeto de seu sistema. Existem três tipos de soluções que são fundamentais para mapear a herança para o modelo relacional: - Mapeamento uma tabela para toda hierarquia: com este método mapeia-se toda a classe de herança para uma tabela, onde todos os atributos de toda a classe da hierarquia são armazenados e, uma coluna OID é introduzida como chave primária na tabela; - Mapeando uma tabela por classe: com este método cada tabela inclui tanto os seus atributos quanto os atributos herdados. Somente as classes “folhas” das hierarquias são mapeadas para tabelas; -Mapeando uma tabela por classe; com este método cria-se uma tabela por classe. A principal vantagem é que esta abordagem é a que esta mais conforme a orientação a objetos. Os registros estão armazenados nas tabelas apropriadas, de acordo com seus 29 papeis. Uma desvantagem, neste método são mais tabelas BD (mais tabelas para manter os relacionamentos). 3.2.3. Mapeando relacionamento de objetos Não somente devemos mapear os objetos para o banco de dados, mas também mapear os relacionamentos que envolvem os objetos. Existem quatro tipos de relacionamento os quais os objetos podem estar envolvidos: generalização, associação, agregação e composição. Para mapear efetivamente esses relacionamentos, devemos estender as diferenças entre eles, como implementar a generalização, e como implementar relacionamento muitos-para-muitos especificamente. Para o banco de dados, perspectivamente, somente tem diferença entre os relacionamentos de associação e agregação/composição e como o objeto é firmemente amarrado a ele. Com a agregação e composição qualquer coisa que você faça com o todo no banco de dados você sempre precisará fazer nas partes, enquanto que com associação este não é o caso. O diagrama de classes é a estrutura das tabelas que são usadas para discutir as varias Maneiras de relacionamentos de objetos um-para-um, um-para-muitos, muitos-paramuitos, que podem ser mapeados para o modelo relacional. No modelo relacional o relacionamento um-para-um, mantém-se, comumente, por meios de colunas de chaves estrangeiras. Esta coluna de chave estrangeira mantém o valor da chave primária (OID do objeto) da coluna (objeto) referenciada. O relacionamento umpara-um pode ser definido como referencia ao atributo, isto pode ser feito transparentemente convertendo a chave estrangeira do objeto referenciado. Isto também possibilita a definição do relacionamento um-para-um no modelo relacional usando uma join table. No modelo objetos existem dois tipos de relacionamento um-para-muitos: agregação (parte de), e associação. Um relacionamento de agregação é definido por meios do próprio atributo e um relacionamento de associação por meios de uma coleção referenciada ao atributo. A diferença entre as duas é que no relacionamento próprio, que é próprio é atualizado no banco de dados. Todos os objetos, em todas as suas coleções, são 30 automaticamente atualizados (este comportamento pode ser modificado em tempo de execução se necessário). No modelo relacional, um-para-muitos pode ser definidos usando a coluna de chave estrangeira ou usando uma join table é uma tabela com o propósito de armazenar os valores mapeados entre a chave estrangeira das duas tabelas envolvidas no relacionamento. Um relacionamento muitos-para-muitos pode ser como uma bi-direcional associação um-para-muitos. Para criar este tipo de relacionamento, simplesmente, definimos o atributo referenciado na coleção, em cada classe envolvida no relacionamento. No modelo relacional um relacionamento muitos-para-mutos pode ser definido usando colunas de chaves estrangeiras ou uma join table. Para usar chaves estrangeiras, uma coluna com a chave estrangeira é definida para cada tabela envolvida no relacionamento. Cada coluna da chave estrangeira mantém a chave da outra tabela. Existem vários tipos que podem ser implementados para associação muitos-para-muitos usando join table. Uma das maneiras de implementar relacionamento muitos-para-muitos é usando uma tabela associativa. O nome da tabela associativa é geralmente a combinação dos nomes das tabelas que estão associadas ou o nome da associação implementada entre elas. A associação original possui um relacionamento muitos-para-muitos e com a utilização da tabela associativa se faz um cruzamento das multiplicações e não se perde essa associação. O propósito de uma tabela associativa é implementar uma associação descrita em uma classe associativa. 3.3. OBJETIVOS GERAIS DOS COMPONENTES MAPEAMENTO OO- ER Mapeamento objeto-relacional é a transformação das regras do modelo objeto para as regras e normalizações do modelo relacional. Isto significa que existem dois modelos utilizados na construção de sistemas. O modelo objeto é utilizado para descrever e apresentar as regras de negócio, enquanto. A utilização do paradigma relacional na persistência dos objetos, atividade importante para definir a base de dados de sistemas que utilizam o paradigma orientado a objeto no seu desenvolvimento. 31 A transformação de uma classe em uma tabela e seus respectivos relacionamentos e atributos em chaves primárias e chaves estrangeiras define, com mais precisão, questões relacionadas à performance no acesso aos dados (não fazer um-para-um). O relacionamento um-para-um, possui algumas desvantagens: 1º são mais tabelas no Banco de Dados (mais tabelas para manter os relacionamentos); 2º o tempo de leitura e escrita de dados será maior usando esta técnica porque varias tabelas serão acessadas. Neste caso, ceve existir a preocupação em atualizar e manipular as classes de nível superior na hierarquias profundas, são requeridos múltiplos joins para recuperar as informações básicas do objeto. Resumindo, o mapeamento OO-ER é importante para que dois mundos consolidados (OO – desenvolvimento e ER – base de dados) possam convergir em uma única solução. O objetivo principal do componente é fazer o mapeamento objeto-relacional utilizando como entrada um arquivo XMI que é o padrão entre intercambio de dados utilizado hoje, e gerando como saída um arquivo .sql para utilização no banco de dados. 3.3.1. Mapeamento básico Cada classe do modelo será transformada em uma tabela do modelo relacional e, todos os seus atributos, serão representados por todas as colunas da tabelas. A Figura 1 apresenta um exemplo de mapeamento básico. A chave primária é definida através da utilização de Object Identification (OID). 32 FIGURA 1 – Mapeamento Básico Geração das Primary Keys (OID): Toda a classe transformada m tabela tem um identificador próprio: o seu OID. O qual facilita o relacionamento entre as tabelas, e com isso, as consultas na maioria dos casos se tornam mais eficientes. 3.3.2. Mapeamento de relacionamentos • Um-para-um - O mapeamento de relacionamentos um-para-um foi implementado no componente utilizando a chave primária da tabela relacionada como chave estrangeira. A figura 2 apresenta um exemplo de mapeamento um-para-um. - Coloca-se a chave primaria de uma tabela como chave estrangeira na outra tabela, independentemente do lado do relacionamento. FIGURA 2 – Mapeamento de relacionamentos (um-para-um) • Um-para-muitos 33 - O mapeamento de relacionamentos um-para-muitos foi implementado no componente utilizando a chave primária da tabela relacionada como chave estrangeira da tabela referenciada. A figura 3 apresenta um exemplo de mapeamento um-para-muitos. - Coloca-se a chave primária de uma tabela como chave na outra tabela, no lado do muitos no relacionamento. FIGURA 3 – Mapeamento de relacionamentos (um-para-muitos) • Muitos-para-muitos - O mapeamento de relacionamento muitos-para-muitos foi implementado no componente utilizando uma tabela associativa. A figura 4 apresenta um exemplo de mapeamento muitos-para-muitos. - Cria-se uma terceira tabela que possui as chaves primárias das duas tabelas relacionadas, como chaves estrangeiras na tabela associativa. O nome da nova tabela é a junção dos nomes das tabelas relacionadas. 34 FIGURA 4 – Mapeamento de relacionamentos (muitos-para-muitos) 3.3.3. Generalização A generalização pode ser mapeada de três formas: uma tabela para toda hierarquia, uma tabela por classe concreta e uma tabela por classe. O componente contempla as três formas de mapeamento. Previamente pode-se definir um mapeamento padrão para a generalização (uso do properties) ou através da escolha, pelo usuário, em tempo de execução. As três formas são: • Uma tabela para toda hierarquia: a tabela pai herda os atributos das tabelas filhas, e permanece o nome da tabela pai, conforme apresenta a figura 5; 35 FIGURA 5 – Uma tabela para toda hierarquia • Uma tabela por classe concreta: as tabelas filhas herdam os atributos da tabela pai e permanecem com os seus nomes. A tabela pai é excluída, conforme apresentado na figura 6; FIGURA 6 – Uma tabela por classe concreta • Uma tabela por classe: para cada classe se gera uma tabela com os seus respectivos atributos, transformados em colunas e, a herança é representada com um relacionamento um-para-um entre o pai e seus filhos. Conforme apresentado na figura 7. 36 FIGURA 7 – Uma tabela por classe 37 3.4. REPRESENTAÇÃO DE UM ESQUEMA RELACIONAL Para que possamos aplicar as regras de mapeamento, consideramos que as relações no esquema relacional de origem estão no mínimo na 2ª forma normal, ou seja, as relações podem conter dependência funcionais transitivas, mas não dependências parciais. Consideramos também que, são conhecidas as chaves primárias e estrangeiras das relações que compõe o esquema relacional de origem. Em relação ao particionamento horizontal de tabelas, consideramos que se tal particionamento existe no esquema relacional de origem, isto foi feito devido á decisão de projeto. Por não conhecemos o motivo de tal decisão, estabelecemos que: se duas tabelas A e B são equivalentes, mas são tratadas como tabelas diferentes devido ao particionamento horizontal, então estas tabelas continuando sendo tratadas como distintas no esquema objeto-relacional, observamos que, não estamos considerando o problema de performance durante o mapeamento, uma vez que a tecnologia objeto-relacional ainda é recente, e não se pode afirmar, deste ponto de vista, qual a melhor estrutura para modelar os dados, diante das diversas possibilidades oferecidas. Entretanto, procuramos fazer o mapeamento oferecendo uma melhor modelagem do ponto de vista de compreensão do esquema. Ainda com o objetivo de minimizar as “falhas” de projeto no esquema relacional de entrada, definimos, a seguir, um procedimento de pré-processamento deste esquema, descrito por; 1. Sejam as relações R1 e R2. Se a chave primária de R1 é também uma chave estrangeira que referencia a chave primária de R2 e a chave primária de R2 é também uma chave estrangeira que referencia a chave primária de R1, então dizemos que as relações R1 e R2 estão particionadas verticalmente. Neste caso, consideramos que as relações R1 e R2 representam uma única relação (R3), cujos atributos dão dados pela união dos atributos de R1 e R2. A chave primária de R3 é a mesma de R1 ( que é a mesma chave primária de R2). Consideramos também, que todas as restrições definidas sobre R1 e R2, tornam-se 38 restrições sobre R3, exceto as restrições de chave estrangeira de R1 para R2 e vice-versa. A relação R3 é então definida: R3 (A, B, C, D, E). A opção de “unir” tabelas particionadas verticalmente deve-se ao seguinte fato: se, aplicando as regras de mapeamento tais tabelas fossem mapeadas em tabelas de objetos distintas, que possuem em identificadores únicos. Desta forma, mesmo que dois objetos possuem a mesma chave-primária, mas em tabelas distintas, então estes objetos possuem OIds diferentes. Conforme definido anteriormente, consideramos que as relações do esquema relacional de entrada podem estar na 2ª forma normal. Desta forma, utilizamos o processo descrito para relações, de tal forma que se na 3ª forma normal. 3.4.1. Algumas regras para mapeamento As regras listadas a seguir, para mapear um esquema relacional em objetorelacional, são baseadas em um conjunto trabalho, com algumas adaptações para adequar o modelo objeto-relacional descrito. A maioria destes trabalhos tratam do mapeamento para estruturas orientadas a objetos. Regra T1: Toda tabela em um esquema relacional que tem somente um atributo na chave primária correspondente a uma tabela de objeto-relacional. Os atributos da tabela relacional tornam-se atributos do tipo de dados estruturados que definem a tabela de objetos. Regra T2: Se a chave primária de uma tabela no esquema relacional tem mais que um atributo, e além disso, a tabela possua outros atributos que não pertençam à chave primária, então esta tabela relacional corresponde a uma tabela de objetos no esquema objetorelacional. Os atributos de chave primária que representam chaves estrangeiras, devem ser mapeados como uma referência ao tipo que define a estrutura de tabelas referenciada pela chave estrangeira. 39 Regra T3: Toda tabela em um esquema relacional, cuja chave primária é composta por mais de um atributo, e todos eles representam chaves estrangeiras. Esta associação é representada no esquema objeto-relacional através de tabelas aninhadas. Regra T4: Toda tabela que possui atributos de chave estrangeira que não fazem parte da chave-primária, estabelece uma associação entre esta tabela e a cada tabela referenciada por chaves estrangeiras. Esta associação é representada no esquema objeto-relacional através de referências (tipo de dados ref.). Regra T5: Todo par de tabelas R1 e R2 que têm as mesmas chaves primárias podem ser envolvidos em um relacionamento de herança. O relacionamento R1 “ é um “ R2 existe se a chave primária de R1 é também a chave estrangeira que refere a tabela R2. Regra T6: Quando um relacionamento de herança esta embutido em uma única tabela , é necessário extrair regras do banco de dados de uma maneira não convencional. Existem vários algoritmos que podem identificar tais regras, denominadas “strong rules” em banco de dados relacionais. Usando-se algum deste algoritmo, pode-se identificar as condições que são estabelecidas para que um atributo, ou conjunto de atributos, possua valor nulo. Identificadas estas regras, podem-se extrair da tabela os relacionamentos de herança nela embutidos. Utilizamos o algoritmo descrito para determinar relacionamento de herança, em um esquema relacional. Regra T7: Seja R uma tabela cuja chave primária tem mais que um atributo e no mínimo um deles não é chave estrangeira, e além disso, R não possui outros atributos que não pertençam à chave primária. Deve-se então acrescentar um atributo na tabela referenciada pela chave primária, cujo domínio é um tipo coleção formado pelos atributos de tabelas, exceto o atributo de chave estrangeira. 40 4. FERRAMENTAS DE MAPEAMENTO OBJETO RELACIONAL 4.1. FUNCIONALIDADES ESSENCIAIS DE UMA FERRAMENTA DE MAPEAMENTO OBJETO/RELACIONAL Já existem hoje no mercado algumas ferramentas capazes de realizar mapeamento entre objeto e registros de tabelas de banco de dados relacionais, fazendo com que operações básicas de cadastro como armazenamento, atualizações, exclusão e consultas possam ser realizadas naturalmente sobre objeto e reflitam por fim em SQL que serão interpretados pelos bancos relacionais. Produtos que realizam mapeamento objeto – relacional integram as capacidades das linguagens de programação orientadas a objeto com sistemas de gerenciamento de bancos relacionais, como Oracle, DB2, Sysbase, e outros. Produtos que realizam mapeamento objeto-relacional são projetados para funcionar bem como linguagens de programação orientadas a objetos. A definição de correspondência entre elementos modelados em objeto e elementos no modelo relacional precisa ser de fáceis configurações. Um grande numero de soluções usa arquivos de configurações XML. Um editor gráfico para edição destes arquivos consiste em uma vantagem extra e terá melhor preferência pela equipe projetista de software para momento de realizar-se uma integração da ferramenta ao ambiente de desenvolvimento. Ferramentas de mapeamento podem ser mais ou menos genéricas e sua capacidade de adaptar-se aos recursos existentes é um importante motivo de escolha. Buscar informações armazenadas em banco de dados relacionais pode prover muita complexidade se a ferramenta de mapeamento por em uso controle de restrições de integridade sobre o modelo relacional usado. A ferramenta deve prover funcionalidade de pesquisa, de montagem de objeto e de atualizações. Estes mecanismos precisam gerenciar corretamente quaisquer problemas de transações e adaptar-se a arquitetura do sistema de informações ao qual se destina, mesmo que seja usado um servidor de aplicação. 41 A descrição dos tipos de dados varia de um modelo para o outro. A ferramenta de mapeamento deve estar apta a diferenciar os tipos de dados e propor correspondência no modelo relacional do banco de dados utilizados. A partir do momento em que uma empresa desenvolvedora de software opta por uma ferramenta de mapeamento objeto/relacional ela geralmente tem como objetivo conseguir as seguintes vantagens no seu processo de engenharia de software: • Melhorar a manutenibilidade de código. O código relacionado a persistência esta embutido em um modulo especifico e não é modificado durante os vários ciclos de vida do desenvolvimento; • O tamanho de código médio seja reduzido uma vez que o código relacionado a persistência é inteiramente genérico e não esteja distribuído por toda a aplicação • O trabalho do(a) desenvolvedor(a) seja facilitado e reduzido, uma vez que ele não tenha mais que com problemas de persistência e possa concentrar-se em resolver problemas de persistência e possa concentrar-se em resolver problemas de regras de negócios; • Aumentar a portabilidade da aplicação: numerosos frameworks de mapeamento objeto/relacional habilitam o uso da maioria dos bancos de dados relacionais existentes no mercado para serem usados transparentemente; A tabela abaixo exibe uma lista com os principais itens que devem ser verificados ao adquirir uma ferramenta de mapeamento objeto/relacional; Características Descrição Herança A ferramenta de desenvolvimento deve ser hábil a projetar um modelo de herança de objetos via relacionamento de tabelas em um banco de dados relacional. Representar relacionamento entre objetos (1-1, 1-M, M-M) Deve saber como projetar relacionamento entre objetos em bancos relacionais. Número de BDs Relacionais suportados Possui suporte aos principais BDs. Relacionais (DB2, Oracle, Informix, MSSQLServer, entre outros) Mecanismos de otimização de performance Quais as funcionalidades para otimizar a performance da ferramentas? Instanciação parcial de objetos, cachê em memória para leitura, etc. 42 Transações Simples A ferramenta suporta o modelo transacional dos BDs Relacionais? Suporte e transações aninhadas A ferramenta suporta o modelo de transações aninhadas BDs Relacionais? 4.2. TIPOS DE FERRAMENTAS 4.2.1. Hibernate É um dos mais bem sucedidos projetos Open Source desenvolvido em Java. Sua facilidade de uso, abstração e transparência fizeram dele quase um padrão em frameworks de mapeamento objeto-relacional. Embora sua documentação seja rica e extensa, a falta de exemplos e dicas em português muitas vezes dificulta a sua adoção por parte de desenvolvedores brasileiros. O Hibernate não trata apenas do mapeamento de classes Java em tabelas de banco de dados (e tipos de dados Java em tipo de dados SQL), mas disponibiliza também um poderoso mecanismo de consulta de dados que pode reduzir significamente o tempo de desenvolvimento. O Hibernate objetiva “liberar” o desenvolvedor em 95% das tarefas comum relacionadas à programação de persistência de dados. O Hibernate disponibiliza um poderoso framework para persistência objetorelacional, de fácil manuseio e alto desempenho. O Hibernate provê suporte para coleções e relacionamentos entre objetos, assim como herança. polimorfismo e composições. Ele também tem um rica linguagem de consulta orientada a objetos, o HQL ( Hibernate Query Language) para recuperação de objetos de banco de dados, uma camada de cachê eficiente e suporte para Java Management Extensions (JMX). O Hibernate possui uma comunidade ativa de usuários que ajuda a prover suporte e ferramentas para extensão. È distribuído de acordo com a Lesser GNU Public License (LGPL), portanto pode ser usado tanto em explicações de código aberto como comerciais. Suporta um grande numero de banco de dados, incluindo Oracle e DB2, assim como banco de dados livres tais como de dados, PostgreSQL e MySQL, permitindo a utilização de meios nativos para a geração de chaves primarias e pessimistic locking além de resolver problemas como pool de conexões e configurações de datasources. Arquitetura 43 Uma visão de alto nível da arquitetura do Hibernate: Figura 8: Uma visão simplista do Hibernate Esta figura mostra o Hibernate usando o banco de dados e a configuração de dados para disponibilizar serviço de persistência (e objetos persistentes) para a aplicação. Dependendo da complexidade do projeto, um maior número de APIs e componentes são utilizados pelo Hibernate. A figura abaixo exibe uma visão mais detalhada da arquitetura: Figura 9: Componentes do Hibernate 44 De acordo com a especificação do Hibernate, estes componentes são definidos da seguinte forma: • SessionFactory (net. sf. hibernate. SessionFactory) Armazena os mapeamentos e configurações compiladas para um banco de dados. É uma fábrica de objetos Session e que provê conexões através do ConnectionProvider. Este objeto é imutável e threadsafe (pode ser acessado por múltiplas threads sem perigo de inconsistência). • Session (net. sf. Hibernate .Session) Um objeto que representa o "diálogo" entre a aplicação e a persistência (banco de dados), encapsulando uma conexão JDBC e manipulado por somente uma thread. Este objeto controla um cache dos objetos persistentes. Os Session não devem durar toda a execução da aplicação, ou seja, são objetos de "vida curta". • Persistent Objects and Collections Objetos manipulados por somente uma thread que contém as informações persistentes e regras de negócio. Devem ser JavaBeans (possuir m construtor sem parâmetros e métodos get/set para os atributos persistidos). Eles só podem estar associados à exatamente uma Session. Assim que o Session é finalizado, estes objetos são liberados para serem usados em qualquer camada da aplicação. • Transient Objects and Collections Instâncias de classes persistentes que não estão atualmente associadas a um Session. Podem ter sido instanciados pela aplicação mas ainda não persistidos, ou eles podem ter sido instanciados por um Session fechado. • Transaction (net.sf.hibernate.Transaction) Objeto usado pela aplicação para especificar unidades atômicas de acesso ao banco de dados. Não deve ser manipulado por múltiplas threads. Abstrai a aplicação dos detalhes das transações JDBC, JTA ou CORBA. Em alguns casos um Session pode manipular vários Transactions. 45 • ConnectionProvider (net.sf.hibernate.connection.ConnectionProvider) Uma fábrica para (e pool de) conexões JDBC. Abstrai a aplicação dos detalhes do Datasource ou DriverManager. Não exposto à aplicação, mas pode ser estendido/implementado pelo desenvolvedor. • TransactionFactory (net.sf.hibernate.TransactionFactory)Uma fábrica para instâncias de Transaction. Não exposto à aplicação, mas pode ser estendido/implementado pelo desenvolvedor. 4.2.2. Instalação e Configuração A última versão Hibernate pode ser copiada do siteoficial(http://www.hibernate.org). A instalação é simples, bastando descompactar o arquivo .zip. O diretório criado contém o JAR núcleo do Hibernate (hibernate2.jar). Também existe um subdiretório chamado lib onde ficam os JARs das outras APIs utilizadas pelo framework. Esses arquivos JARs devem ser referenciados no classpath da aplicação. É também necessário que a classe de driver do seu banco de dados esteja no classpath. O Hibernate foi desenvolvido para operar em vários ambientes diferentes. Por isso, existe um grande número de parâmetros de configuração. Por outro lado, a grande maioria dos parâmetros tem valor padrão e, além disso, o Hibernate é distribuído com um arquivo chamado hibernate.properties que mostra as várias opções disponíveis. Você apenas precisa colocar esse arquivo no classpath e customizá-lo. Configurando o SessionFactory A tabela abaixo mostra os parâmetros mais importantes para configuração da conexão JDBC (com banco de dados): 46 Tabela 2: Parâmetros principais para configuração da conexão JDBC O Hibernate possui ainda uma série de parâmetros que definem seu comportamento em tempo de execução, como por exemplo: Tabela 3: Parâmetros que definem comportamento em tempo de execução O Hibernate trabalha com dialetos para um grande número de bancos de dados, tais como: DB2, MySQL, Oracle, Sybase, Progress, PostgreSQL, Microsoft SQL Server, Ingres, Informix entre outros. Todos estes parâmetros são usados na configuração inicial. A partir deles, é criado um objeto da classe SessionFactory. Este objeto contém todas as informações passadas na configuração. Quando criado, o SessionFactory carrega e verifica todas as configurações. Esta operação consome mais tempo do Hibernate e por isso é recomendável criar este objeto somente uma vez e utilizá-lo durante toda execução da aplicação. O Hibernate permite que sejam criados mais de um SessionFactory, mas isso só deve ser feito se houver necessidade de acesso a mais de um banco de dados. Existem duas formas de informar ao SessionFactory as configurações do Hibernate: 4.2.3. Configuração através do objeto Configuration A configuração pode ser feita através da classe Configuration (net.sf.hibernate.cfg.Configuration). Os objetos desta classe podem ser instanciados de forma direta. Configuration deve receber as configurações gerais através da classe Properties e também os mapeamentos Objeto/Relacional (ORM). Na tabela 4 um exemplo de como criar um SessionFactory através do objeto Configuration: Tabela Exemplo 4: Criando um SessionFactory através do objeto Configuration 47 4.2.4. Mapeamento O/R Básico O Hibernate usa arquivos XML para mapear os atributos das classes em campos das tabelas. Qualquer classe pode ser mapeada desde que seja um Bean, ou seja, possua um construtor sem parâmetros e os atributos mapeados possuam métodos get e set. A presença de um atributo de identificação (chave primária) é altamente recomendada, mas não é obrigatória, caso contrário diversas funcionalidades não estarão disponíveis. Não é necessário nenhum tipo de especialização (herança) ou realização de interface para que uma classe seja persistida pelo Hibernate. Isto quer dizer que o Hibernate pode persistir objetos POJO (Plain Old Java Object). Abaixo o exemplo de uma classe que poderia ser persistida: Tabela Exemplo 5: Classe candidata à persistência Abaixo, a implementação da classe do diagrama UML acima. Tabela Exemplo 6: Implementação da classe Pessoa Segue a declaração de criação da tabela que armazena a classe acima. 48 Aconselha-se escolher identificadores de tipos de grande capacidade (como BIGINT) para que um grande número de registros possa ser armazenado. Nota-se o atributo AUTO_INCREMENT para a chave primária idPessoa, logo em seguida fica claro o porquê disso. Tabela Exemplo 7: Declaração de criação da tabela que armazena a classe Pessoa Para mapear uma classe numa tabela: Tabela Exemplo 8: Mapeando a classe Pessoa numa tabela Os arquivos de mapeamento são fáceis de configurar e divididos de maneira bastante intuitiva. Além disso, a maioria dos parâmetros existentes possui valores padrão. Este exemplo inicial exibe apenas os parâmetros obrigatórios (com exceção do elemento <id>). É obrigatório especificar o nome da classe e a tabela com a qual está associada. O elemento <id> indica qual é o identificador do objeto e como ele é criado e obtido. Caso ele seja omitido, o Hibernate considera que a classe não possui identificador. O atributo name indica que atributo da classe será usado como identificador. O elemento generator informa como serão gerados os identificadores dos novos elementos. Segue alguns dos tipos de geradores existentes no Hibernate: 49 Tabela 9: Tipo de geradores existentes no Hibernate Cada atributo da classe é mapeado através do elemento <property>. O único parâmetro obrigatório é o nome do atributo. O Hibernate tentará encontrar uma coluna com este mesmo nome e definir seu tipo por reflexão. Abaixo, o mapeamento desta mesma classe com os parâmetros opcionais (usando o mapeamento simples para executar como exemplo, nota-se que os nomes das colunas foram trocados). Tabela Exemplo 10: Mapeamento da classe Pessoa com parâmetros opcionais O primeiro parâmetro a notar é column. Ele indica a coluna correspondente ao atributo da classe na tabela, caso não tenham o mesmo nome. O parâmetro type, indica o 50 tipo do atributo Hibernate. Abaixo a associação entre os tipos do Hibernate mais comuns, as classes wrapper Java e os tipos no banco de dados: Tabela 11: Associação entre tipos do Hibernate, classes wrapper Java e tipos no BD No mapeamento, pode ser informado na propriedade type tanto o tipo Hibernate quando a classe Java. Outra propriedade opcional importante é not-null. Ela indica que no momento da persistência de um objeto, o determinado atributo pode ser nulo ou não. Caso um atributo esteja indicado como not-null=”true” e no momento do salvamento ele esteja null, Hibernate irá lançar uma exceção. É essencial lembrar de incluir a linha abaixo no arquivo do hibernate.cfg.xml. Ela indica que este mapeamento deve estar contemplado no SessionFactory. Tabela Exemplo 12: Mapeamento contemplado no SessionFactory 51 4.3. Hibernate / Persistência Hibernate é um mecanismo simples e poderoso que permite a persistência de objetos em banco de dados relacionais de maneira transparente para qualquer tipo de aplicação Java. Esta ferramenta, que pode ser baixada gratuitamente da Internet através do endereço http://hibernate.sf.net/, possibilita que os objetos possam ser gravados e recuperados a partir de um banco de dados sem que o desenvolvedor tenha que se preocupar com muitos detalhes. Não há necessidade de se implementar mapeamentos hardcoded no código Java. O Hibernate resolve problemas como pool de conexões e configurações de Datasources. Em linhas gerais, a codificação de um sistema pode ser dividida em duas partes: regras de negócio e serviços de infra-estrutura. Regras de negócio, como o próprio nome diz, estão relacionadas ao negócio com o qual o sistema visa trabalhar. Já os serviços de infra-estrutura estão relacionados à segurança, cache, transação, serviços de nomes, etc. A idéia do Hibernate é permitir que o desenvolvedor mantenha seu foco sobre as regras de negócio, liberando-o de parte das tarefas de infra-estrutura. O Hibernate suporta alguns dos principais bancos de dados relacionais disponíveis no mercado, permitindo a utilização de meios nativos para geração de chaves primárias e pessimistic locking. O hibernate trabalha com os bancos de dados através de dialetos, conforme a tabela 1. Banco de dados DB2 MySQL SAPDB Oracle Sybase Progress McKoiSQL Interbase/Firebird Pointbase PostgreSQL HypersonicSQL Microsoft SQL Server Dialeto cirus.hibernate.sql.DB2Dialect cirus.hibernate.sql.MySqlDialect cirus.hibernate.sql.SAPDBDialect cirus.hibernate.sql.OracleDialect cirus.hibernate.sql.SybaseDialect cirus.hibernate.sql.ProgressDialect cirus.hibernate.sql.McKoiDialect cirus.hibernate.sql.InterbaseDialect cirus.hibernate.sql.PointbaseDialect cirus.hibernate.sql.PostgreSQLDialect cirus.hibernate.sql.HSQLDialect cirus.hibernate.sql.SybaseDialect Tabela 13 – Bancos de dados suportados pelo Hibernate 52 Fonte: www.mundojava.com.br O desenvolvimento usando Hibernate é um processo que pode ser dividido em cinco etapas. O primeiro passo é a construção do banco de dados com o qual a aplicação irá trabalhar, ou seja, criar as tabelas onde os objetos serão persistidos. Este banco de dados, com suas entidades, atributos e relacionamentos, poderá ser criado de forma tradicional ou, a critério do usuário, poderá ser utilizada a ferramenta SchemaExport que acompanha o Hibernate, Esta ferramenta gera o esquema de banco de dados baseado no relacionamento entre os objetos que se quer persistir. O segundo passo é criação dos objetos cujos estados vão ser persistidos, isto é, a construção de classes (beans) para cada entidade do banco de dados. Estas classes devem ser construídas seguindo o modelo JavaBeans, com métodos get e set para manipulação dos atributos. Neste ponto, o Hibernate difere-se de outros mecanismos de persistências como o JDO, visto que os beans utilizados pelo Hibernate não necessitam estender superclasses ou implementar interfaces. É necessária a implementação de um construtor default (construtor sem parâmetros) para todas as classes persistentes. O Hibernate faz a instanciação de objetos e o acesso às suas propriedades através de reflexão, assim métodos de acesso e construtores não necessitam ser declarados como públicos. Adicionalmente, deve ser criada uma propriedade “chave-primária”, que fará às vezes de uma primary key, através da qual um objeto será unicamente identificado. Esta propriedade, não obrigatória, pode ser do tipo primitivo int, long, char ou mesmo uma String. Após a criação das tabelas e dos beans é necessário criar meta-dados de modo a fornecer ao Hibernate informações sobre como relacionar os objetos com as entidades do banco de dados. Esta é a terceira etapa, a criação de arquivos XML que relacionam as propriedades de cada objeto aos campos das tabelas. Nestes arquivos de mapeamento devese informar, ainda, qual propriedade do objeto se relaciona com a chave-primária, bem com os relacionamentos entre entidades (inclusive os tipos de relacionamento: 1-1, 1-N ou NN). A quarta etapa refere-se à criação de um arquivo contendo as propriedades necessárias para que o Hibernate se conecte ao banco de dados. Existe um arquivo de 53 configuração modelo (hibernate.properties) que poderá ser utilizado como base para que o usuário proceda à configuração. A quinta e última etapa é a criação de Data Access Objects (DAO), Tais mecanismos são design pattern úteis para separação da lógica de acesso a dados da lógica de negócios da aplicação. Estas classes é que conterão os métodos de inclusão, alteração, exclusão dos objetos, etc. Em resumo, o Hibernate é uma ferramenta que permite trabalhar com persistência sobre banco de dados, sem necessidade da inclusão de instruções SQL em meio ao código Java, assim como elimina a necessidade de se mapear ResultSets e implementar configuração de pool de conexões, etc., o que torna o código mais legível e, conseqüentemente, mais fácil de manter. Contudo a implementação não é independente da fonte de dados, visto que o Hibernate trabalha apenas com alguns dos bancos de dados relacionais. Por outro lado, caso a aplicação exija consultas SQL complexas, há de se considerar a utilização da linguagem HQL, a Query Language do Hibernate. Tabela 14 - Comparações do Hibernate com outros frameworks de persistência 54 4.5. OJB O OJB (Object Relational Bridge) faz parte do projeto Jakarta, da fundação Apache, que desenvolve ferramentas de código aberto para Java. Prestes a ter sua primeira versão oficial lançada o OJB é baseado em um cerne de mapeamento objeto relacional a partir do qual várias APIs podem ser disponibilizadas. O OJB e' muito flexivel na forma em que pode ser usado. O desenvolvedor tem opções de 3 diferentes API: Uma API compativel com o padrao ODMG 3.0 Uma API compativel com o padrão JDO da Sun (ainda incompleta). A PersistenceBroker API que server como o núcleo das demais APIs usada no OJB. Essa API pode ser usada por qualquer tipo de aplicação. Podemos considerar OJB comparado ao Hibernate em termos de suporte aos conceitos OO e funcionalidades. O OJB apresenta as vantagens de possuir melhor suporte a distribuição (com sincronização de cachê) e ser aderente a padrões estabelecidos. O mapeamento objeto-relacional do OJB é feito através de um único arquivo XML, cujas marcações fazem parte de um DTD próprio, e para o mapeamento de hierarquias de classe são permitidos todos os tipos de particionamento (tipado, vertical e horizontal). Inicialmente foi tentado o mapeamento sobre a mesma base de dados utilizada nos testes do Hibernate, com particionamento vertical. Contudo, testes preliminares detectaram algumas falhas do OJB na execução deste tipo de mapeamento. Devido a este problema o programa de carga utilizado para a geração da base de dados de testes do Hibernate foi modificado para que também fossem geradas na base tabelas que suportassem o particionamento horizontal das classes. A base de dados passou então a ser usada tanto pelo Hibernate como pelo OJB, porém com mapeamentos sobre conjuntos diferentes de tabelas. A implementação do modelo de classes, em comparação com o Hibernate, foi um pouco mais complicada. No caso do OJB também foram utilizadas classes simples, apenas com atributos protegidos e métodos de acesso e modificação. Contudo, por uma restrição do OJB, para que fosse possível o uso de sua funcionalidade de carga de objetos sob demanda foi necessário a definição de interfaces, e fazer que as classes as implementassem, também, de forma semelhante ao caso do Hibernate, não foi possível declarar algumas das classes como abstratas. 55 Cliente CLIENT APPLICATION OJB Layers Backends Componentes do OJB ● O JDO Suporta: – Hypersonic SQL – Lutris InstantDB – IBM DB2 – Oracle – MS Access – MS SQL Server 2000 -Instalar/Configurar 56 Toda a funcionalidade da versao 1.0 ja esta presente na versao atual. Baixe a versao binaria do OJB, a menos que você queira dar uma olhada na fonte. O arquivo que esta disponível para download no web site tem valioas arquivos p/ configurar e criar os tutoriais, e algumas aplicações dentro do OJB. Vou descrever todos os arquivos necessários para uma aplicação OJB rodar. -Arquivos Esses são os arquivos JAR que você precisa no classpath de uma aplicação OJB: antlr.jar commons-collections-2.0.jar commons-dbcp.jar commons-lang-1.0-mod.jar commons-pool.jar db-ojb-1.0.rc2.jar jta-spec1_0_1.jar O arquivo OJB.properties contem configurações especificas de como o ambiente OJB deve rodar. Nele você configura que pool de conexões quer usar, qual a política de cachê a ser usada, em que arquivo esta a configuração do banco de dados (chamada de repositório), e varias outras opções. Em geral, não se precisa mudar nada nesse arquivo, mas, ter uma boa noção e' importante p/ saber que partes do OJB você pode configurar. Chegou a vez do arquivo mais importante, onde se configura a aplicação que esta sendo desenvolvida, e o repository.xml, ele contem chamada a outros arquivos XML, sendo: -repository_database.xml, contem a configuração do acesso ao banco de dados. Aqui, você especifica o banco, usuário, senha, enfim, as configurações normais de uma conexão JDBC. Também se pode setar opções do pool de conexões, como a quantidade máxima e inicial de conexões a serem abertas. 57 -repository_user.xml, contem o mapeamento das classes Java as tabelas no banco de dados. Para cada classe, você deve criar uma definição como esta a seguir: Code: <class-descriptor class="br.com.javafree.ojb.Produto" table="jf_produto" > <field-descriptor id="1" name="produtoId" column="produto_id" jdbc-type="INTEGER" primarykey="true" autoincrement="true" /> <field-descriptor id="2" name="descricao" column="produto_desc" jdbc-type="VARCHAR" /> </class-descriptor> Nesse exemplo, temos uma classe br.com.javafree.ojb.Produto que corresponde a tabela jf_produto, os campos produto_id e producto_desc correspondem as propriedades id e descrição na classe. A fase de construção do repository_user.xml e' um pouco trabalhosa, mas, os desenvolvedores do OJB esta criando uma aplicação para criar o XML quando você indica a base de dados, o que agilizara muito esse processo. Chegou a vez do código, vou mostrar treis exemplos de operação no banco de dados, uma parte do código é sempre necessária no uso do OJB, que é a chamado ao PersistenceBroker, classe responsável pela operações no banco de dados. Os pacotes org.apache.ojb.broker e org.apache.ojb.broker.query tem que ser importados. SELECIONAR TODOS OS REGISTROS E IMPRIMIR Code: PersistenceBroker broker = null; // indicamos que classe queremos buscar no banco de dados Query query = new QueryByCriteria(br.com.javafree.ojb.Produto.class, null); try { broker = PersistenceBrokerFactory.defaultPersistenceBroker(); // pedimos ao broker p/ gerar uma colecao como os dados do BD Collection allProducts = broker.getCollectionByQuery(query); // simplesmente imprimos as propriedades dos objetos retornados java.util.Iterator iter = allProducts.iterator(); while (iter.hasNext()) { br.com.javafree.ojb.Produto produto = (br.com.javafree.ojb.Produto)iter.next(); out.println(produto.getId() + " - " + produto.getDescricao() + "<br>"); } } catch (Throwable t) { 58 t.printStackTrace(); } INSERIR UM NOVO REGISTRO Code: // try to save to DB using OJB br.com.javafree.ojb.Produto novoProduto = new br.com.javafree.ojb.Produto(); novoProduto.setDescricao("Livro de Java "); // ojb PersistenceBroker broker = null; try { broker = PersistenceBrokerFactory.defaultPersistenceBroker(); broker.store(novoProduto); } catch(Exception e) { e.printStackTrace(); } Para inserir um registro, e' mais simples ainda, basta chamar o método store() do PersistenceBroker que o OJB se encarrega de armazenar os dados no BD de acordo com o que definido no arquivo repository_user.xml. Percebam que, pelo fato de termos definido o campo produto_id como auto incremento, o próprio OJB se encarrega em calcular essa valor usando a configuração no arquivo OJB.properties. Esse recurso (auto-incremento) exige o uso de uma tabela interna do OJB, que pode ser criada com o seguinte comando SQL, no meu caso, p/ MySQL: Code: CREATE TABLE `ojb_hl_seq` ( `TABLENAME` varchar(175) NOT NULL default '', `FIELDNAME` varchar(70) NOT NULL default '', `MAX_KEY` int(11) default NULL, `GRAB_SIZE` int(11) default NULL, PRIMARY KEY (`TABLENAME`,`FIELDNAME`) ) 5. ESTUDO DE CASO 5.1. DIAGRAMA DE CLASSE “BIBLIOTECA MÚSICA” Na tabela 15 estão as classe que vão ser utilizada no mapeamento das ferramentas estudadas. 59 TABELA 15 Diagrama de Classe “Biblioteca Música”; 5.1.2. Modelo de Mapeamento Hibernate: Definição da classe Artista; Import java.uitl.Set; Import java.util.HashSet; Public class Artista{ private Long id; private String nome; private Set cds = new HashSet(); } Mapeamento da classe Artista <?xml version”1.0” encoding= “UTF-8”?> // cabeçalho do xml <!DOCTYPE hibernate-mapping PUBLIC // cabeçalho do hibernate “-//Hibernate/Hibernate Mapping DTD 2.0//EN” “http://hibernate.sourceforge.net/hibernate.mapping-2.0.dtd”> <hibernate-mapping> <class name= “Artista” table=”artista”> <id name= “id” column=“id” type=“long” unsaved-value= “null”> <generator class=“native”/> <id> 60 <property Name= “nome” Type= “java.lang.String” Column= “nome” Length= “255” Not-null= “true” /> <set name= “cds”lazy= “true” inverse= “true”> <key column= “artista_id”/> <one-to-many class= “Cd”/> </set> </class> </hibernate-mapping> Definição da classe Cd; Import java.uitl.Set; Import java.util.HashSet; Public class Cd{ private Long id; private String titulo; private Capa capa; private Artista artista; private Set cds = new HashSet(); } Mapeamento da classe Cd; <?xml version”1.0” encoding= “UTF-8”?> // cabeçalho do xml <!DOCTYPE hibernate-mapping PUBLIC // cabeçalho do hibernate “-//Hibernate/Hibernate Mapping DTD 2.0//EN” “http://hibernate.sourceforge.net/hibernate.mapping-2.0.dtd”> <hibernate-mapping> <class name= “Cd” table=”cd”> <id name= “id” column=“id” type=“long” unsaved-value= “null”> <generator class=“native”/> <id> <property Name= “titulo” 61 Type= “java.lang.String” Column= “titulo” Length= “255” Not-null= “true” /> <set name= “musicas”lazy= “true” inverse= “true”> <key column= “cd_id”/> <one-to-many class= “Musica”/> </set> <many-to-one name=”artista” column= “artista_id” not-null= “true”> <one-to-one name= “capa” class= “Capa”/> </class> </hibernate-mapping> Definição da classe Capa; Import java.uitl.Set; Import java.util.HashSet; Public class Capa{ private Long id; private String imagem; private Cd cd; } Mapeamento da classe Capa; <?xml version”1.0” encoding= “UTF-8”?> // cabeçalho do xml <!DOCTYPE hibernate-mapping PUBLIC // cabeçalho do hibernate “-//Hibernate/Hibernate Mapping DTD 2.0//EN” “http://hibernate.sourceforge.net/hibernate.mapping-2.0.dtd”> <hibernate-mapping> <class name= “Capa” table=”capa”> <id name= “id” column=“id” type=“long” unsaved-value= “null”> <generator class=“native”/> <id> <property Name= “imagem” Type= “java.lang.String” 62 Column= “imagem” Length= “255” Not-null= “true” /> </class> </hibernate-mapping> Definição da classe Musica; Public class Musica{ private Long id; private String titulo; private Cd cd; } Mapeamento da classe Musica; <?xml version”1.0” encoding= “UTF-8”?> // cabeçalho do xml <!DOCTYPE hibernate-mapping PUBLIC // cabeçalho do hibernate “-//Hibernate/Hibernate Mapping DTD 2.0//EN” “http://hibernate.sourceforge.net/hibernate.mapping-2.0.dtd”> <hibernate-mapping> <class name= “Musica” table=”musica”> <id name= “id” column=“id” type=“long” unsaved-value= “null”> <generator class=“native”/> <id> <property Name= “titulo” Type= “java.lang.String” Column= “titulo” Length= “255” Not-null= “true” /> <many-to-one name=”cd” column= “cd_id” not-null= “true”> </class> </hibernate-mapping> 63 5.2. Modelo de mapeamento OJB Definição da classe Artista; Import java.uitl.Set; Import java.util.HashSet; Public class Artista{ private Long id; private String nome; private Set cds = new HashSet(); } Mapeamento da classe Artista <class-descriptor Class=”br.com.javafree.ojb.produto” Table=”jf_produto”> <class name= “Artista” table=”artista”> <id name= “id” column=“id” type=“long” unsaved-value= “null”> <generator class=“native”/> <id> <field-descriptorid=”1” Name= “nome” Type= “java.lang.String” Column= “nome” Length= “255” Not-null= “true” /> <class-descriptor> Definição da classe Cd; Import java.uitl.Set; Import java.util.HashSet; Public class Cd{ private Long id; private String titulo; private Capa capa; private Artista artista; 64 private Set cds = new HashSet(); } Mapeamento da classe Cd; <class-descriptor Class=”br.com.javafree.ojb.produto” Table=”jf_produto” > <field-descriptorid=”1” <class name= “Cd” table=”cd”> <id name= “id” column=“id” type=“long” unsaved-value= “null”> <generator class=“native”/> <id> <property Name= “titulo” Type= “java.lang.String” Column= “titulo” Length= “255” Not-null= “true” /> <set name= “musicas”lazy= “true” inverse= “true”> <key column= “cd_id”/> <one-to-many class= “Musica”/> </set> <many-to-one name=”artista” column= “artista_id” not-null= “true”> <one-to-one name= “capa” class= “Capa”/> /> <class-descriptor> Definição da classe Capa; Import java.uitl.Set; Import java.util.HashSet; Public class Capa{ private Long id; private String imagem; private Cd cd; } 65 Mapeamento da classe Capa; <class-descriptor Class=”br.com.javafree.ojb.produto” Table=”jf_produto” > <field-descriptorid=”1” <class name= “Capa” table=”capa”> <id name= “id” column=“id” type=“long” unsaved-value= “null”> <generator class=“native”/> <id> <property Name= “imagem” Type= “java.lang.String” Column= “imagem” Length= “255” Not-null= “true” /> <class-descriptor> Definição da classe Musica; Public class Musica{ private Long id; private String titulo; private Cd cd; } Mapeamento da classe Musica; <class-descriptor Class=”br.com.javafree.ojb.produto” Table=”jf_produto” > <field-descriptorid=”1” <class name= “Musica” table=”musica”> <id name= “id” column=“id” type=“long” unsaved-value= “null”> <generator class=“native”/> <id> 66 <property Name= “titulo” Type= “java.lang.String” Column= “titulo” Length= “255” Not-null= “true” /> <class-descriptor> 5.3. Essas são as tabelas do SQL CREATE TABLE Cd ( ID INT NOT NULL, TITULO VARCHAR(30) NOT NULL, CAPA INT NOT NULL, ARTISTA INT NOT NULL PRIMARY KEY (ID), FOREIGN KEY (CAPA) REFERENCES Capa(ID), FOREIGN KEY (ARTISTA) REFERENCES Artista(ID) ); CREATE TABLE Capa ( ID INT NOT NULL, IMAGEM VARCHAR(50), CD INT NOT NULL PRIMARY KEY (ID), FOREIGN KEY (CD) REFERENCES Cd(ID) ); CREATE TABLE Musica ( ID INT NOT NULL, TITULO VARCHAR(30) NOT NULL, CD INT, PRIMARY KEY (ID), FOREIGN KEY (CD) REFERENCES Cd(ID) ); 67 CREATE TABLE Artista ( ID INT NOT NULL, NOME VARCHAR(30), PRIMARY KEY (ID) ); 5.4. Assim seria a inserção nas tabelas uma a uma INSERT INTO Artista (ID, NOME) VALUES (55, ‘João’); INSERT INTO Musics (ID, TITULO, CD) VALUES (123654, ‘MTV AO VIVO’, 53651); 6. RESULTADOS OBTIDOS Todos os testes foram executados em um mesmo ambiente. Para a execução de cada consulta foi adotado o seguinte procedimento: (1) iniciar o SGBD; (2) iniciar o programa 68 de execução da consulta; (3) finalizar o SGBD; (4) executar a leitura de arquivos em disco, não relacionados aos testes, para invalidação de cachês e buffers do sistema operacional. Antes da execução dos testes de desempenho, porém, foi feito também um teste inicial para que fosse comprovada a consistência de resultados entre os diversos sistemas testados. Os conteúdos dos resultados das consultas foram tomados como referência e posteriormente comparados com os consteúdos dos resultados das consultas executadas pelo Hibernate e o OJB. O Hibernate e o OJB apresentam funcionalidades semelhantes aos resumos, chamadas de consultas escalares e consultas de relatório, respectivamente. O Hibernate e o OJB também apresentam funcionalidades para distribuição, sendo o Hibernate mais restritivo e o OJB tendo suporte direto a sincronização de cachê e controle de concorrência distribuído. 6.1. Vantagens e desvantagens da utilização do mapeamento usando a ferramenta Hibernate Usando a ferramenta: O Hibernate torna transparente o acesso ao banco de dados, sendo que ele faz tudo sozinho, ou seja, ele salva tudo na memória e quando você termina ele salva tudo no banco. O código fica menor e mais eficiente pois o Hibernate otimiza o código. Você usa o SQL mas as tabelas são montadas pelo Hibernate. Com Hibernate você montaria os objetos mas a inserção ele pegaria cada atributo e montaria os comandos inserts sozinho. Não Usando a ferramenta: O código seria bem maior. Teria que inserir toda as classes usando o SQL sendo que eu teria que montar as tabelas sendo que o Hibernate já te entrega pronto. Todo o acesso ao banco seria manual. 69 6.2. Vantagens e desvantagens da utilização do mapeamento usando a ferramenta OJB Usando a ferramenta: Fácil de utilizar O código fica bem menor Ele inseri no banco de dados altomâticamente Não Usando a ferramenta: Não tem a característica de um banco de dados robusto Por exemplo: indexs, transaccion... Não é escalavel O código ficaria bem maior A inserção ao banco de dados seria manual 7. Conclusão 70 Apesar da relutância de alguns em adotar esquemas de persistência, fica evidente que sua utilização trás um ganho considerável de tempo na implementação de um sistema e eleva a qualidade do produto final, à medida que diminui a possibilidade de erros de codificação. O fraco acoplamento entre as camadas de dados e de lógica do sistema promovido pelas Camadas de Persistência é outro ponto que demonstra a sua utilidade. Além de fornecer um acesso mais natural aos dados, as Camadas de Persistência executam controle transacional, otimização de consultas e transformações automáticas de dados entre formatos distinto (tabelas relacionais para arquivos XML ou classes Java, por exemplo). Sem dúvida, as Camadas de Persistência devem funcionar como a principal ponte de ligação entre sistemas Orientados a Objetos e repositórios de dados diversos: um conceito poderoso, com implementações estáveis e comprovadamente eficientes. 71 8. REFERÊNCIA BIBLIOGRÁFICA AGILE DATA. “Object-Relational Mapping – An Essay”. [Internet: http://www.agiledata.org/essays/mappingObjects.html, recuperado em 20/01/2005]. AMBYSOFT. [Internet:http://www.ambysoft.com/mappingObjects.html, recuperado em 20/01/2005]. DATE, C. J. “Introdução a Sistemas de Bancos de Dados”. Rio de Janeiro: Campus, 1990. HIBERNATE. [Internet: http://www.hibernate.org, recuperado em 16/02/2005]. NASSU, E. A.; SETZER, W. W. “Banco de Dados Orientado a Objeto”. Editora Edgar Bkucher Ltda., 1º edição 1999. SILBERSCHATZ, A.; KORTH, H. F., SUDARSHAN, S. “Sistema de Banco de Dados”. São Paulo: Makron Books, 1999. MUNDO-OO.“Mapeando Objetos para Bancos de Dados Relacionais: técnicas e implementações.”[Internet:http://www.mundooo.com.br/php/mooartigos.php?pa=show page&pid=19, recuperado em 15/03/2005]. ULBRA“Persistência de Dados”[Internet:http://www.ulbra.tche.br/~roland/tcc- gr/monografias/2003-2-tc2-Anelise_Rosa_Rodrigues.pdf, recuperado em 08/04/2005]. INTERSYSTEMS.“Problemas de Impedância”[Internet:http://www.intersystems.com.br /isc/downloads/cachedoctec/ProblemadeImpedância.pdf, recuperado em 08/04/2005]. JAKARTA PROJECT. “Projeto Jakarta” [Internet: http://www.jakarta.apache.org recuperado em 10/10/2005]. 72