Artigo: Mapeando Objetos para Bancos de Dados Relacionais: técnicas e implementações. Autor: Herval Freire de A. Júnior – é programador Java, web components developer certificado pela Sun e graduado em Tecnologia em Telemática pelo CEFET-PB. Publicado: http://www.mundooo.com.br/php/mooartigos.php?pa=showpage&pid=19 Data Publicação: 23/02/2003 Mapeando Objetos para Bancos de Dados Relacionais: técnicas e implementações Introdução O surgimento do desenvolvimento "Orientado a Objetos" ocasionou uma mudança radical na estruturação e organização da informação. Entretanto, os bancos de dados utilizados continuaram sendo relacionais. Devido a isto, é comum a adaptação dos modelos de objetos na tentativa de compatibilizá-los com o modelo relacional. Além disso, é notório o esforço aplicado no processo de persistência manual dos objetos no banco de dados – onde os desenvolvedores precisam dominar a linguagem SQL e utilizá-la para realizar acessos ao banco de dados. Como conseqüência ocorre uma redução considerável na qualidade do produto final, construção de uma modelagem "orientada a objetos" inconsistente e a um desperdício considerável de tempo na implementação manual da persistência. Para permitir um processo de mapeamento entre sistemas baseados em objetos e bases de dados relacionais, foram propostas diversas idéias que encaminhar-se para o conceito de Camada de Persistência. Evolução dos modelos de Persistência de Objetos Na grande maioria dos sistemas, incluir código SQL para acesso ao SGBD em meio ao restante da lógica do sistema é a solução adotada, graças à rapidez de implementação. Esta solução é perigosa, pois implica, muitas vezes, no acoplamento do sistema ao SGBD utilizado, o que dificulta a manutenção do código. Além disso, qualquer mudança na estrutura das tabelas existentes no Banco de Dados trazem o caos à aplicação. Com intuito de diminuir este acoplamento, surge a opção de separar o código SQL das classes da aplicação, de forma que as alterações no modelo de dados requerem modificações apenas nas classes de acesso a dados (Data Access Classes), restringindo o impacto das mudanças no sistema como um todo. No entanto, apesar da limpeza de código e melhor divisão de responsabilidades, a solução está longe de ser ideal, por ainda manter os dois mundos (objetos e dados relacionais) intimamente ligados. A principal função de uma camada de abstração de acesso a dados é garantir aos desenvolvedores de software a total independência entre o modelo de objetos e o esquema de dados do banco, permitindo que a base ou detalhes do esquema de dados sejam substituídos sem impacto nenhum na aplicação. Camadas 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. A utilização deste 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 realizar consultas que retornam coleções de objetos instanciados. Vantagens da utilização As vantagens do uso de uma Camada de Persistência são: isolar os acessos realizados diretamente ao banco de dados na aplicação, bem como centraliza os processos de construção de consultas (queries) e operações de manipulação de dados (insert, update e delete) em uma camada de objetos inacessível ao programador. Requisitos de uma Camada de Persistência Segundo Scott Ambler, uma Camada de Persistência características: Dar suporte a diversos tipos de mecanismos de persistência Encapsulamento completo da camada de dados Ações com multi-objetos Transações Extensibilidade Identificadores de Objetos real deve implementar as seguintes Cursores e Proxies Registros Arquiteturas Múltiplas Diversas versões de banco de dados e fabricantes Múltiplas conexões Queries SQL Controle de Concorrência Object IDs Os Object Ids (OIDs) são identificadores únicos de elementos do esquema de dados. Eles funcionam como um tipo de chave primária nos sistemas Orientados a Objetos, mantendo relacionamentos entre objetos e garantindo a unicidade dos objetos em todo o esquema. Mapeando objetos para Tabelas 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 e o desenvolvedor que for utilizá-la deve organizar as tabelas em seu banco de dados de acordo com o esquema de objetos. 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 e o comprimento máximo dos campos. Também é importante ressaltar que, nem sempre atributos de um objeto são obrigatoriamente uma coluna em uma tabela. Além disso, existem casos onde um atributo pode ser mapeado para diversas colunas ou vários atributos podem ser mapeados para uma mesma coluna. Mapeamento de classes em tabelas As três técnicas de mapeamento de objetos mais comumente implementadas são: Mapeamento de uma tabela por hierarquia: Toda a hierarquia de classes deve ser representada por uma mesma tabela: uma coluna que identifique o tipo do objeto (Object Type) serve para identificar a classe do objeto representado por cada linha na tabela, quando nenhum outro modo de identificação é viável. As desvantagens são: a ausência de normalização e para hierarquias de classes com muitas especializações existe uma proliferação de campos com valores nulos. Mapeamento de uma tabela por classe concreta: Nesta estratégia, teremos uma tabela 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. Essa estratégia leva à redundância de dados: quaisquer atributos definidos em uma classe abstrata na hierarquia devem ser criados em todas as tabelas que representam classes-filhas da mesma. Além disso, mudar o tipo (especializar ou generalizar) de 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. Mapeamento de uma tabela por classe: 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 estratégia, 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. A quantidade de junções (joins) entre tabelas para obter todos os dados de um objeto o seu principal ponto negativo. A tabela a seguir, faz um comparativo destas três técnicas de mapeamento de classes. Ad-hoc reporting Facilidade de implementação Facilidade de acesso Acoplamento Velocidade de acesso Suporte a polimorfismos Uma tabela por hierarquia de classes Simples Simples Simples Muito alto Rápido Médio Uma tabela por classe concreta Médio Médio Simples Alto Rápido Baixo Uma tabela por classe Médio/Difícil Difícil Médio/Simples Baixo Médio/Rápido Alto Mapeamento de Relacionamentos Relacionamentos um-para-um necessitam que uma chave estrangeira seja colocada em uma das duas tabelas, relacionando o elemento associado na outra tabela. 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. No caso de relacionamentos muitos-para-muitos, convenciona-se criar uma tabela intermediária que armazene pares de chaves, identificando os dois lados do relacionamento. Camadas de Persistência e Linguagens de Programação Existem diversas implementações (bibliotecas) de camadas de persistência. Estas bibliotecas muitas vezes tratam da geração dos esquemas de dados automaticamente e podem realizar engenharia reversa do banco de dados, criando a hierarquia de classes a partir do esquema de tabelas do banco de dados. Abaixo, segue a lista com algumas das implementações mais utilizadas. Hibernate – 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 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 para tratar a persistência, gerando código SQL à medida que for necessário. Castor – um framework de ligação de dados (databinding), que 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. Object-Relational Java Bridge (OJB) - projeto do grupo Apache para prover uma implementação open-source dos padrões de mapeamento de objetos ODMG e JDO. 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 mesmo em ambientes J2EE. Conclusão Apesar da relutância em adotar esquemas de persistência, fica evidente que sua utilização trás um ganho considerável reduzindo o tempo de implementação e aumentando a qualidade do produto final, à medida que diminui a possibilidade de erros de codificação. Além de fornecer um acesso mais natural aos dados, as Camadas de Persistência podem ainda executar controle transacional, otimização de consultas e transformação automática de dados entre formatos distintos (tabelas relacionais para arquivos XML ou classes Java, por exemplo).