CURSO SUPERIOR DE TECNÓLOGIA EM DESENVOLVIMENTO DE SOFTWARE PERSISTÊNCIA DE OBJETOS ATRAVÉS DO FRAMEWORK HIBERNATE FRANCISCO AUGUSTO FRANCO DO NASCIMENTO CAMPOS DOS GOYTACAZES – RJ 2005 2 FRANCISCO AUGUSTO FRANCO DO NASCIMENTO PERSISTÊNCIA DE OBJETOS ATRAVÉS DO FRAMEWORK HIBERNATE: UMA VISÃO DO MAPEAMENTO OBJETORELACIONAL Monografia apresentada ao Centro Federal de Educação Tecnológica de Campos como requisito parcial para conclusão do Curso Superior de Tecnologia em Desenvolvimento de Software Orientador: Profº Marcelo Machado Feres Mestre em Engenharia de Software/ EMN-VUB CAMPOS DOS GOYTACAZES – RJ 2005 3 Nascimento, Francisco Augusto Franco. Persistência de objetos através do framework Hibernate / Francisco Augusto Franco do Nascimento / Campos dos Goytacazes/RJ, 2005. 65f.: Monografia (Tecnologia em Desenvolvimento de Software) – Centro Federal de Educação tecnológica de Campos, Campos dos Goytacazes/ RJ. Bibliografia: f. 78-80. Orientador: Profº Marcelo Machado Feres. 1. Prática docente. 2. Procedimentos Metodológicos. 3. Intervenção Social. I.Título. 4 Este trabalho, nos termos da legislação que resguarda os direitos autorais, é considerado propriedade institucional. É permitida a transcrição parcial de trechos do trabalho ou menção ao mesmo para comentários e citações desde que não tenha finalidade comercial e que seja feita a referência bibliográfica completa. Os conceitos expressos responsabilidade do autor. neste trabalho são de 5 Monografia intitulada A prática do professor de literatura brasileira sob a ótica dos alunos elaborada por Maria Emília Rocha e apresentada publicamente perante a Banca Avaliadora, como parte dos requisitos para conclusão do Curso de Licenciatura em Língua Portuguesa e Literatura Brasileira da Universidade Santa Teresinha. Aprovada em 22 de dezembro de 2005 Banca Avaliadora: ................................................................................................................................... Profº Marcelo Machado Feres (orientador) Mestre em Engenharia de Software/EMN-VUB Centro Federal de Educação Tecnológica de Campos/RJ ................................................................................................................................... Profª Aline Pires Vieira de Vasconcelos Mestre em Engenharia de Software/ EMN-VUB Centro Federal de Educação Tecnológica de Campos /RJ ................................................................................................................................... Profº Maurício José Viana Amorim Mestre em Sistemas e Computação/IME Centro Federal de Educação Tecnológica de Campos /RJ 6 “Na natureza nada se cria, nada se destrói, tudo se transforma !” (Lavoisier) 7 DEDICATÓRIA Dedico esta monografia para minha esposa Glett, que com muito amor, carinho e paciência me apoiou e não me deixou desanimar. 8 RESUMO NASCIMENTO, Francisco Augusto Franco do. Persistência de objetos através do framework Hibernate: uma visão do mapeamento objeto-relacional. Campos dos Goytacazes,RJ: [s.n.], Centro Federal de Educação Tecnológica de Campos, 2005. Monografia (Tecnologia em Desenvolvimento de Software). Palavras-chaves: persistência de objetos; mapeamento objeto-relacional; framework Hibernate. Neste trabalho, procurou-se fazer uma introdução a tecnologia persistência de objetos em banco de dados relacionais. Para este fim, utilizou-se a técnica de mapeamento objeto-relacional. Foi utilizando como exemplo de estudo, o framework Hibernate. Através dele pode-se entender todas as fases deste processo bem como, uma abordagem de utilização. Pode-se entender como aplicar os conceitos da orientação a objetos, aplicados a persistência em diferentes formas de configuração da solução. 9 ÍNDICE DE FIGURAS Figura 1: Visão geral da arquitetura de uma aplicação usando o Hibernate..................... 21 Figura 2: Diagrama de Classes da Universidade............................................................... 31 Figura 3: Diagrama de Tabelas do Banco de Dados (DED).............................................. 32 10 ÍNDICE DE LISTAGENS Listagem 1: Script de Criação do Banco de Dados 33 Listagem 2: Listagem simplificada da classe Pessoa 39 Listagem 3: Endereco.hbm.xml - Mapeamento da classe Endereco 41 Listagem 4: Professor.hbm.xml - Mapeamento da classe Professor 44 Listagem 5: Aluno.hbm.xml - Mapeamento da classe Aluno 45 Listagem 6: Detalhe de Professor.hbm.xml - Mapeamento 1:N com a classe 46 Turma Listagem 7: Exemplo de código mostrando a importância de utilizar o 47 “inverse” Listagem 8: Exemplo de código mostrando a utilização de “set” 48 Listagem 9: Turma.hbm.xml - Mapeamento da classe Turma 49 Listagem 10: hibernate.cfg.xml – Configuração do HIbernate 51 Listagem 11: HibernateUtility.java – Classe para configurar e abrir sessões do 56 Hibernate Listagem 12: Teste1.java – Teste de utilização do Hibernate para persistência 57 de uma instância da classe Curso Listagem 13: Teste2.java – Teste para consultar dados de uma Turma 59 usando HQL Listagem 14: Teste3.java – Teste para consultar dados de uma Turma 60 usando Criteria API Listagem 15: Trecho para consultar as 10 primeiras turmas 61 Listagem 16: Trecho para consultar turmas usando parametro nomeado 61 Listagem 17: Trecho mostrando a utilização de “query” para consultas 62 externas Listagem 18: Trecho mostrando a utilização de consultas externas nomeadas 63 11 SUMÁRIO ÍNDICE DE FIGURAS ............................................................................................... IX ÍNDICE DE LISTAGENS ............................................................................................ X CAPÍTULO 1 – INTRODUÇÃO ................................................................................. 13 1.1 – Desenvolvimento de Aplicações OO........................................................ 13 1.2 – Java ......................................................................................................... 14 1.3 – Mapeamento Objeto-Relacional .............................................................. 14 CAPÍTULO 2 – PERSISTÊNCIA DE OBJETOS....................................................... 16 2.1 – Requisitos de Uma Camada de Persistência ........................................... 16 2.2 – Serialização ............................................................................................. 18 2.3 – Banco de Dados OO e Relacionais Estendidos ....................................... 19 2.4 – Banco de Dados Relacionais ................................................................... 20 2.5 – Padrões e Frameworks de Persistência em Java .................................... 21 2.5.1 – EJB CMP/CMR.................................................................................. 21 2.5.2 – JDO ................................................................................................... 22 2.5.3 – Framework Hibernate ........................................................................ 23 2.7 – Considerações Finais .............................................................................. 23 CAPÍTULO 3 – HIBERNATE: ARQUITETURA E CARACTERÍSTICAS ................. 24 3.1 – Arquitetura do Hibernate .......................................................................... 24 3.1.1 – Persistência e Eficiência ....................................................................... 25 3.1.2 – Características ...................................................................................... 27 Programação Orientada a Objetos ................................................................ 27 Suporte para Modelos de Objetos Específicos ............................................. 28 Sem Aumento de Tempo na Construção da Aplicação ................................. 28 Alta Escalabilidade ........................................................................................ 28 A Linguagem de Busca (Query Language) ................................................... 28 Gratuito e Aberto (Open Source) .................................................................. 28 Portabilidade ................................................................................................. 28 CAPÍTULO 4 – HIBERNATE: EXEMPLO DE UTILIZAÇÃO .................................... 30 4.1 – Descrição de um modelo de Universidade .............................................. 30 4.2 – Definindo os Objetos do Modelo .............................................................. 31 4.3 – Definido as Tabelas do Banco de Dados ................................................. 32 4.4 – Mapeamento das Tabelas para as Classes ............................................. 35 4.4.1 – Conceito de Identidade ..................................................................... 36 4.5 – Mapeamento de um Relacionamento 1:1 ................................................ 37 4.6 – Mapeamento de Herança ........................................................................ 41 4.7 – Mapeamento de Associações 1:N e N:N ................................................. 44 4.8 – Configuração do Hibernate 3 ................................................................... 48 CAPÍTULO 5 – FUNCIONAMENTO DO HIBERNATE ............................................. 51 5.1 – Realização de Pesquisas no Banco usando o Hibernate ........................ 54 5.2 – Restrições as Pesquisas.......................................................................... 59 CAPÍTULO 6 – CRIANDO UMA APLICAÇÃO COMPLETA USANDO O HIBERNATE ............................................................................................................. 61 6.1 – Descrevendo o código da classe AplicacaoUniversidade ........................ 67 12 6.2 – Descrição da classe Leitor ....................................................................... 69 6.3 – Descrevendo o código da classe ListarCurso .......................................... 70 6.4 – Execução da aplicação ............................................................................ 72 6.5 – Recuperação das informações ................................................................ 74 6.6 – Conclusão ................................................................................................ 78 CAPÍTULO 7 – CONCLUSÃO .................................................................................. 79 8.1 – Considerações Finais .............................................................................. 80 REFERÊNCIAS BIBLIOGRÁFICAS: ........................................................................ 82 ANEXO ..................................................................................................................... 85 ANEXO I: HIBERNATE: INSTALAÇÃO ................................................................... 86 CAPÍTULO 1 – INTRODUÇÃO 1.1 – Desenvolvimento de Aplicações OO O aumento da complexidade do software, de sua aplicação em diversos setores da sociedade, a grande concorrência entre empresas de desenvolvimento de software, todos estes fatores levaram a uma preocupação crescente com vários aspectos do desenvolvimento de sistemas. Assim, questões relativas ao tempo e custo de desenvolvimento, tecnologias utilizadas e adoção de ferramentas de apoio tornaram-se decisivas para o sucesso de uma solução. A adoção de boas práticas pregadas pela Engenharia de Software pode auxiliar a atingir esses objetivos, possibilitando a construção de sistemas com um menor custo, mais confiáveis e de boa qualidade. Atualmente, a principal prática adotada pelas empresas de desenvolvimento de software para obter melhorias em seus produtos é o paradigma da orientação a objetos (OO), através da programação orientada a objetos (POO) e análise e projeto orientados a objetos (APOO). Tudo isto seria a solução definitiva se não fosse por um grande porém: As aplicações legadas, que há muitos anos estão em produção, e que contemplam anos e anos de dados armazenados. Em geral, elas são tão grandes e estão de tal forma estáveis, que não seria viável re-desenvolver uma outra solução mais atualizada para resolver o mesmo problema. Sem contar o fato, de que muitas 14 vezes, toda a estratégia de negócios da companhia pode estar sendo sustentado, por tais aplicações. Neste contexto, todo desenvolvimento novo irá de alguma forma, ter que se preocupar com as aplicações já existentes, seja para obter insumo para o processamento, seja para descarregar a informação já processada. A escolha de uma estratégia de solução, capaz de enfrentar este problema e de ser duradoura, pode e deve ser um fator preponderante no processo decisório. Visto que novas tecnologias, que pregam a solução de grandes problemas, tendem a não manter a compatibilidade com o que já esta presente na organização. 1.2 – Java O trabalho adota a linguagem de programação Java, uma linguagem que vem sendo amplamente empregada no desenvolvimento de software para inúmeras aplicações e que inclui diversos conceitos e facilidades que refletem o estado da arte em programação. Ela está disponível na maioria dos sistemas operacionais, e nas mais importantes arquiteturas tais como Windows, Linux e Solaris. A linguagem de programação Java, por implementar fielmente vários conceitos de orientação a objetos, como classes, herança, encapsulamento e polimorfismo, naturalmente oferece um bom suporte para reuso e manutenção. O Java possui um rico conjunto de bibliotecas e outras ferramentas para desenvolvimento de programas, as quais encontram-se facilmente disponíveis através da Internet, juntamente com uma extensa documentação sobre a linguagem e sobre essas ferramentas. 1.3 – Mapeamento Objeto-Relacional A introdução da orientação a objetos no contexto de desenvolvimento provocou mudanças significativas na forma como os softwares são produzidos e mantidos. O paradigma da orientação a objetos está focado em analogias a coisas e comportamentos do mundo real, o que de fato facilita o desenvolvimento, 15 a manutenção e a evolução das aplicações melhorando a qualidade do produto final. Em conseqüência, muitas áreas do desenvolvimento de software estão sendo revisadas, pois práticas, teorias e técnicas que eram adequadas, não podem ser aplicadas de forma irrestrita, quando se trata de software OO. Uma dessas áreas é a que trata da persistência das informações. Inicialmente deve-se definir claramente o conceito de persistência. Persistência é o que faz com que uma informação solicitada uma vez, esteja disponível outras vezes na mesma aplicação, ou até mesmo para outras aplicações. Adaptando-se ao universo dos objetos, as informações estão distribuídas em objetos, então, persistência dos objetos é o que faz com que um objeto solicitado uma vez, esteja disponível outras vezes na mesma aplicação, ou até mesmo para outras aplicações. Existem diversas alternativas que podem ser utilizadas para solucionar o problema relativo à persistência dos objetos. Atualmente, a tecnologia mais utilizada para esse fim são os Sistemas Gerenciadores de Bancos de Dados Relacionais (SGBDR), ou simplesmente Bancos de Dados Relacionais. Outrora no passado, SGBDR’s se posicionaram solidamente no mercado, através de produtos robustos e seguros, com uma linguagem específica de consultas e manipulação, o SQL (“Strutured Query Language”, ou seja, Linguagem Estruturada de Consultas), que se tornou um padrão de mercado. Segundo Alvim e Oliveira (2003), “os Sistemas Gerenciadores de Bancos de Dados Relacionais conquistaram um lugar de destaque em comparação a outras tecnologias de armazenamento de dados. Embora estes produtos realizem seu papel de modo satisfatório no mundo para o qual foram criados, quando utilizados no contexto OO adicionam complexidade extra, pois a aplicação passa a necessitar de um processo intermediário de conversão” (Alvim & Oliveira, 2003) [3], este processo é conhecido como mapeamento objeto-relacional. CAPÍTULO 2 – PERSISTÊNCIA DE OBJETOS “Persistência é a capacidade de um objeto de sobreviver fora dos limites da aplicação que o criou” (Martins, V; 1999) [2]. Normalmente isto significa que o objeto, ou melhor o estado dele, tem que ser gravado em um meio de armazenamento persistente, como por exemplo o disco. Quando a aplicação necessita novamente deste objeto, ela solicita que seu estado seja recuperado e a partir disto, ele é novamente criado na memória, estando disponível para a aplicação que o solicitou. O ideal seria o uso de uma plataforma de desenvolvimento que permita que o mesmo tipo de abstração possa ser aplicado em todas as etapas do ciclo de vida do desenvolvimento de um software. Como esta ainda não é uma possibilidade trivial, será feita uma analise das possibilidades mais comuns para resolver esta questão, além da forma de implementar a persistência em uma aplicação orientada a objetos. 2.1 – Requisitos de Uma Camada de Persistência A camada de persistência deve oferecer os mecanismos necessários para gravar permanentemente o estado dos objetos e recuperar novamente o mesmo 17 estado. Ela deve prover os serviços básicos de CRUD (create, read, update, delete). create() - Cria uma chave primária para o objeto e armazena ele no banco. read() - Diz ao objeto para recuperar os dados gravados. update() - Grava qualquer alteração feita no objeto. delete() - Remove o objeto do banco. A implementação mais simples é fazer com que cada vez que a informação de um objeto seja modificada, ela seja alterada no banco de dados. Este tipo de persistência parece útil mas em sistemas grandes se torna ineficiente pois deixará o sistema lento por consumir muitos recursos. O ideal é fazê-lo de tempos em tempos. A camada de persistência também é responsável por manter a segurança dos dados, a integridade, a consistência, a performance, o travamento (locking) e o suporte a transações. Uma camada de persistência deve possuir: 1. Serviços Básicos de CRUD – Incluir, remover, atualizar, etc. 2. Encapsulamento completo dos mecanismos de persistência – Apenas deve-se mandar mensagens como save, delete e retrieve... 3. Ações Multi-objetos – A camada de persistência deve dar suporte à recuperação de vários objetos simultaneamente. 4. Transações – A camada de persistência deve garantir que uma ação deve ter sido feita com sucesso ou deverá fazer um roll back. 5. Capacidade de Extensão – Deve permitir adicionar novas classes e deve permitir mudanças fáceis no mecanismo de persistência. 6. Identificadores de Objeto – Um número (OID) que identifica o objeto como chaves primárias/estrangeiras deve ser gerado. 7. Cursores – Um conceito que evita que centenas ou milhares de objetos sejam recuperados de uma vez. 8. Proxy – Um objeto que representa simplificadamente o objeto a ser recuperado, evitando overhead. 18 9. Registros – permitir retornar registros ao invés de objetos. 10. Múltiplas arquiteturas – Permitir mudanças de arquiteturas (centralizada para cliente/servidor, ou 2-tier, ou n-tier) Várias versões de banco de dados – Habilidade para 11. facilmente mudar os mecanismos de persistência para outro fabricante ou versão de banco de dados. Múltiplas conexões – Permitir conexões em vários bancos ao 12. mesmo tempo. Drivers nativos e não nativos – Acessar o banco de várias 13. maneiras. SQL queries – permitir usar diretamente códigos SQL. 14. Dessa forma, considerando a persistência dos objetos, temos as seguintes alternativas de solução: Utilizar a serialização de objetos; Utilizar banco de dados capaz de armazenar e recuperar objetos, tais como banco de dados orientados a objetos e relacionais estendidos; Utilizar banco de dados relacional tradicional; 2.2 – Serialização A opção mais simples de se fazer a persistência de um objeto é através da serialização e deserialização de objetos. Este mecanismo permite que objetos sejam empacotados com informação suficiente que permitam posterior reconstrução. A serialização permite que objetos sejam transformados em Streams (Fluxos de dados, strings de caracteres) que por sua vez podem ser transmitidos através da rede ou serem gravados em um meio de armazenamento. Um objeto serializado é um grafo que inclui dados da classe e todas as suas dependências. Isto é uma desvantagem muito grande porque se a classe ou suas dependências mudarem, o formato usado na serialização mudará e os novos objetos serão incompatíveis com os antigos, e não será mais possível recuperar arquivos gravados com a versão antiga. 19 2.3 – Banco de Dados OO e Relacionais Estendidos Os Sistemas Gerenciadores de Bancos de Dados Orientados a Objetos (SGBDOO) fornecem transparência, pois não existe processo de conversão, já que este já foi criado para persistir os objetos. Eles implementam de forma natural os modelos orientados a objetos, isto é, os conceitos de classe, objeto, tipo, identidade, igualdade, encapsulamento, herança, agregação, método e polimorfismo (overloading, overriding e late binding). Além disso, possuem mecanismos especiais para controlar transações entre objetos, técnicas de armazenamento que facilitam a recuperação rápida de objetos complexos (clustering) e funções adicionais para coordenar atividades cooperativas, tais como mecanismos para avisar os usuários sobre mudanças de estado nos objetos e para notificar a disponibilidade dos objetos. O acesso aos objetos é mais versátil quando se utiliza um SGBDOO, já que é possível fazê-lo de forma navegacional, ou seja, seguindo o grafo de objetos; ou por meio de linguagens do tipo SQL. Uma outra possível alternativa, os bancos de dados relacionais estendidos, estes permitem a adição, a manipulação e a pesquisa eficiente de novos tipos de dados à medida que eles se fazem necessários. Assim surgiram os sistemas gerenciadores de bancos de dados objeto-relacional (SGBDOR), também chamados SGBDR’s Estendidos. Estender, neste contexto, significa adicionar tipos de dados definidos pelo usuário (UDT’s – User-Defined Datatypes), funções definidas pelo usuário (UDF’s – User-Defined Functions), métodos de acesso definidos pelo usuário e alterar o otimizador de query que também deve ser extensível. Usuários e aplicações simplesmente invocam UDF’s e não precisam entender sua estrutura interna. É evidente que tanto um quanto o outro, estão mais preparados para fazer o mapeamento objeto-relacional, de uma forma mais automátizada, capazes de entender muitos tipos complexos como som e imagem. O importante é observar que estas opções, embora disponíveis há algum tempo não conquistaram o mercado, ocupando quase que um nicho muito específico. Isto aconteceu devido 20 a grande quantidade de dados armazenados em bancos de dados relacionais, além do elevado grau de aceitação que os SGDBR´s conquistaram no mercado. 2.4 – Banco de Dados Relacionais Uma solução para usar software OO e de manter a base relacional instalada é a criação de classes ou framework de mapeamento objeto-relacional. No entanto, a criação e manutenção dessas classes aumentam o trabalho de codificação. Isto aumenta o risco de inserção de defeitos no software. O modelo relacional está baseado em uma única estrutura de dados: a relação. Uma relação é uma tabela com linhas (tuplas) e colunas (atributos), as quais contêm um tipo de dado simples especificado (integer, character string,...). Aqui começa o problema de impedância entre a tecnologia de objetos e os bancos de dados relacionais. Os objetos podem ter estruturas complexas e, neste caso, será necessário um esforço adicional de programação para adaptá-los ao modelo relacional antes de armazená-los e outro para adequá-los novamente ao modelo de objetos no momento de recuperá-los. Esses esforços estão totalmente dissociados dos requisitos de negócio, trata-se de programação voltada à solução de problemas tecnológicos. As soluções geralmente adotadas envolvem descrever uma relação de equivalencia entre os atributos do objeto e as colunas de uma ou mais tabelas no banco relacional. Essa relação pode ser descrita através de código SQL, em que a aplicação é responsável por persistir e recuperar os objetos diretamente no banco. Quando o desenvolvedor opta por fazer a persistência "manualmente" usando o código SQL, perde-se muito tempo desenvolvendo código-fonte para recuperar e salvar os dados em banco e a aplicação fica muito vulnerável a pequenos erros de programação típicos de trabalhos repetitivos e “copy-andpaste” (ou seja, copiar-e-colar). Além de se ter este requisito tecnológico misturado com os requisitos do negócio, dificultando as manutenções e futuras evoluções da aplicação. Compromete a flexibilidade, como por exemplo, a troca de banco de dados, se necessário. Alternativamente pode-se utilizar framework’s, ou camadas de persistência capazes de realizarem o mapeamento objeto-relacional e vice-versa. Um 21 framework ou camada é “uma aplicação reusável, semicompleta que pode ser especializada para produzir aplicações customizadas” (Martins, V; 1999) [2]. Estes frameworks visam automatizar esta tarefa, reduzindo o esforço de integração entre estes dois mundos, o orientado a objetos e o relacional, através de uma camada intermediária entre a aplicação (cliente/servidor ou web) e o banco de dados, que permita acessar as informações do banco de dados sem usar SQL e que, além disso, possibilite a troca por algum outro banco de dados facilmente, sem a alteração no código da aplicação. 2.5 – Padrões e Frameworks de Persistência em Java Tendo-se com base a linguagem Java, existem algumas alternativas de camadas de persistência, que podem ser utilizadas para o desenvolvedor implementar o mapeamento de objetos. Elas podem ser mais ou menos complexas quanto ao seu funcionamento. Segundo Lozano [4], as principais tecnologias para camada de persistência no Java são as seguintes: EJB CMP/CMR (Enterprise JavaBeans, Container Managed Persistence/ Container Managed Relationship); JDO (Java Data Objects); Framework Hibernate; 2.5.1 – EJB CMP/CMR Para entender este mecanismo, é necessário primeiro entender o que é um EJB. Um EJB é um componente do lado do servidor de aplicação que encapsula a lógica de negócios de uma aplicação. Eles precisam ser construidos de acordo com as especificações EJB e só são distribuídos e executados em um EJB container. O EJB container oferece serviços no nível de sistema, como transações, para aplicativos EJB. Mas o interessante é um tipo de EJB chamado Entity Bean. Um Entity Bean é um componente de dados que armazena permanentemente os dados em uma estrutura secundária, geralmente um banco de dados. 22 Em um aplicativo EJB, um Entity Bean representa um registro de uma tabela de banco de dados. Logo, ao invés de manipular dados diretamente no BD, eles são manipulados nos Entity Beans. O Container Managed Persistence é a criação automática de lógica de persistência pelo servidor de aplicação. O container se encarrega de criar as tabelas (no deployment), inserir os dados removê-los e alterá-los. A CMP é utilizada na construção de aplicações totalmente independentes de banco de dados. Ela tem uma API intrusiva, na qual o objeto a ser persistido deve ser desenvolvido e códificado especificamente para este mecanismo. Também se considera que um objeto persistente é antes disso, um componente distribuído (remoto). Esta característica exige o uso de um servidor de aplicações. A maioria das soluções CMP/CMR usa geração de código para implementar o código de persistência. Ela é uma tecnologia bastante madura, com recursos avançados de otimização e gerenciamento na maioria dos produtos disponíveis no mercado. Uma desvantagem da CMP/CMR é que ela tem uma curva de aprendizado bastante longa. O padrão atual (2.1) peca por não especificar como é o mapeamento OO/Relacional, gerando dependência em relação ao servidor de aplicação (descritores proprietários). Segundo Lozano [4], uma outra desvantagem, é o fato de que ela “tem má fama no mercado, por causa de limitações da versão 1.x, que não tinha recursos para relacionamentos entre Entity Beans”. A versão 3.0 é baseada no fato de que objetos persistentes não são expostos para a camada de apresentação (cliente) no modelo MVC. 2.5.2 – JDO Inicialmente foi criado como alternativa ao CMP, para aplicações que não rodam no servidor de aplicações, embora ele também é bem-integrado em alguns servidores de aplicações. Tem uma API não intrusiva, diferentes implementações utilizam manipulação de bytecodes (bytecode decoration) ou pré-processamento. O padrão atual peca por não definir o mapeamento OO/Relacional, mas o problema será resolvido na versão 2.0 da especificação. 23 2.5.3 – Framework Hibernate Não é padrão do JCP (Java Community Process), mas é quase um padrão de fato do mercado. Ganhou muito espaço por causa do “preconceito” contra EJBs e da padronização incompleta do CMP e do JDO. Devido a problemas técnicos a maioria da JCP está a favor do Hibernate, ao invés do JDO. A versão 3.0 está melhorando a documentação e recursos de otimização. Ele usa reflexão e geração de bytecodes em tempo de execução, fazendo isto de maneira mais transparente, a comunicação entre a aplicação e o banco de dados, para o usuário final. Ele foi incorporado ao JBoss 4.0 como base do seu mecanismo CMP/CMR. É famoso pela sua flexibilidade em termos de linguagem de consulta e API. 2.7 – Considerações Finais A tendência é que com o passar do tempo, o CMP/CMR ficará semelhante ao Hibernate e ao JDO. A nova versão dele também terá um padrão para o mapeamento objeto-relacional, compartilhado com o JDO 2.0 Neste trabalho optou-se por se estudar o Hibernate, por ser um projeto OpenSource (ou seja, Código Aberto) e Free (ou seja, Licença Gratuita), sendo que ele foi desenvolvido inteiramente em linguagem Java. Devido a este fato, examinaremos mais a fundo este framework, que possui vantagens que seram citadas em detalhes posteriormente, para assim entender quais as características que o diferenciam dos seus concorrentes. CAPÍTULO 3 – HIBERNATE: ARQUITETURA E CARACTERÍSTICAS Para desenvolver aplicações de forma mais produtiva, sem a necessidade de realizar mapeamentos entre diferentes tipos de modelos (modelo de objetos x modelo relacional), a tarefa de persistência deveria ser transparente ao desenvolvedor. Este se preocuparia principalmente com a modelagem dos objetos do domínio e sua codificação, enquanto os dados seriam gerenciados através de uma camada de persistência de objetos. O Hibernate é um framework para realizar o mapeamento objeto-relacional em Java. Ele transforma os dados tabulares de um banco de dados em um grafo de objetos definido pelo desenvolvedor. Com isso, ele se livra de escrever todo o código de acesso ao banco de dados e de SQL, acelerando a velocidade do desenvolvimento. 3.1 – Arquitetura do Hibernate O Hibernate constitui-se de uma biblioteca de classes ou framework de código-aberto (open source) para mapeamento objeto-relacional, que foi criado para Java, desenvolvido totalmente em Java, se colocando como uma camada 25 adicional entre a aplicação e o banco de dados. Seu objetivo é prover uma camada que permita a utilização de um mecanismo de persistência de objetos, realizando assim o mapeamento automático entre o modelo relacional e o modelo de classes da aplicação. Sendo assim, eliminando a existência de instruções SQL nas classes do domínio. 3.1.1 – Persistência e Eficiência O projeto Hibernate foi criado para reduzir o tempo que o programador gasta em suas tarefas relacionadas à persistência de dados no desenvolvimento de um software orientado a objetos, interagindo com um banco de dados relacional. Ele proporciona uma solução aos ambientes Java de mapeamento objeto/relacional, abstraindo toda a representação de tipo de dados através de arquivos XML (Extensible Markup Language). A estrutura de uma aplicação desenvolvida com o Hibernate é mostrada na Figura 1. Através dessa abstração, o Hibernate se torna responsável por gerar o SQL necessário à aplicação, poupando o trabalho manual de programação da conversão dos dados relacionais em objetos. 26 Figura 1: Visão geral da arquitetura de uma aplicação usando o Hibernate A idéia é tornar totalmente transparente para o desenvolvedor o acesso ao banco de dados. Por exemplo, uma classe chamada Cliente mapeada em uma tabela chamada Clientes, não se vai trabalhar diretamente com a tabela e nem se vai abrir conexão com o banco de dados. Na verdade, devem ser realizados três passos: 1) Mapear a tabela em um arquivo XML utilizado pelo Hibernate (XML mapping – Figura 1); 2) Gerar as classes baseadas nos arquivos XML das tabelas; 3) Usar as rotinas do Hibernate para as operações de busca, cadastro ou alteração de dados. Para cadastrar um novo cliente, não é realizada a operação de INSERT, e sim alterados os valores da classe Cliente que fará uso automaticamente das rotinas do Hibernate. A princípio parece mais trabalhoso, mas na realidade é muito prático e produtivo trabalhar usando meia dúzia de linhas de código do Hibernate, a ficar chamando via seu código, rotinas de abrir conexão na base, ou carregar os valores de uma linha em um atributo, etc. 27 Na figura 1, dois componentes sobressaem: Hibernate.properties; XML mapping. O hibernate.properties é o arquivo que contém as informações sobre a conexão com o banco de dados. Nele definimos o tipo do banco, dialeto SQL do banco (dialect), driver, url, nome do usuário de conexão (username) e senha de conexão (password). O Hibernate suporta muitos SGBD´s. Para facilitar o processo de configuração do arquivo hibernate.properties, são fornecidos modelos que devem ser customizados pelo desenvolvedor. No XML mapping são registradas as informações a respeito do mapeamento das classes para suas respectivas tabelas relacionais. Cada classe é mapeada para uma ou mais tabelas. Além disso, são registradas informações sobre os relacionamentos, cardinalidades e identificadores, entre outras. Vale notar que as classes do Hibernate realizam a leitura desses arquivos em tempo de execução, permitindo alterações sem a necessidade de recompilação. 3.1.2 – Características Existem seis principais características que dão destaque ao Hibernate. Estas características são apresentadas em mais detalhes a seguir: Programação Orientada a Objetos O Hibernate suporta características de uma linguagem orientada a objetos como herança (de várias maneiras), associação (um-para-um ou um-para-muitos e agregação) e composição. Isso ajuda muito no desenvolvimento, pois o programador não precisa se preocupar com o mapeamento para uma base relacional. Podemos Implementar uma classe pai Pessoa que tem como filhos as classes PessoaFisica e PessoaJurídica e o Hibernate faz o mapeamento desses dados. 28 Suporte para Modelos de Objetos Específicos O Hibernate possui uma rica variedade de mapeamentos para variáveis compostas do Java: Map, Set, List, SortedMap, SortedSet, array e Collection. Sem Aumento de Tempo na Construção da Aplicação Não existe geração de código a mais ou processamento a mais para construir sua aplicação pelo fato de estar utilizando o framework. Ou seja, quando o seu software é compilado, não é necessária a recompilação do Hibernate. Alguns frameworks necessitam ser recompilados junto com a aplicação para funcionar. Alta Escalabilidade O Hibernate possui uma ótima performance, com uma arquitetura de cache de duas camadas, podendo ser usado até em cluster. A Linguagem de Busca (Query Language) O hibernate soluciona ambos os problemas: não só como buscar os objetos do banco de dados, mas também como popular dados. O Hibernate é equipado com uma poderosa linguagem de busca (o HQL, Hibernate Query Language) que intensionalmente é muito parecida com o SQL, mas é mais orientada a objetos, facilitando os programadores Java que não estão familiarizados com o SQL. Gratuito e Aberto (Open Source) O Hibernate é licenciado pela LGPL (Lesser GNU Public License). Portabilidade O Hibernate é portável para todos os bancos compatíveis com o padrão SQL. Isso significa que sua aplicação pode ser desenvolvida usando um 29 inicialmente um banco de dados e depois, com alguns ajustes nos arquivos do Hibernate (e não no código), ser portada para outro banco de dados relacional, tal como Oracle, DB2, MySQL, PostgreSQL, Sybase, SAP DB, HypersonicSQL, Microsoft SQL Server, Progress, Mckoi SQL, Pointbase, Interbase, etc. CAPÍTULO 4 – HIBERNATE: EXEMPLO DE UTILIZAÇÃO Para se avaliar melhor as capacidades do Hibernate e os procedimentos necessários para a construção de uma camada de persistência, a melhor alternativa é utilizar-se de um modelo simplificado como exemplo. De tal forma, a maioria das atividades básicas do desenvolvedor podem ser detalhadas, facilitando a compreensão do processo em que elas estão inseridas . 4.1 – Descrição de um modelo de Universidade Uma universidade quer um sistema para controlar sua parte docente e discente. O sistema deverá controlar os seguintes processos: Montagem da grade de disciplina dos cursos, com cada disciplina sua ementa; Criação das turmas por disciplinas; Matrícula do aluno na turma; Alocação do professor na turma; Atualização de endereços (rua, número, complemento, bairro, cidade, estado, CEP) alunos e professores. As informações que se deseja controlar são nome, telefone, e-mail; 31 4.2 – Definindo os Objetos do Modelo O modelo é simples, ele modela um cadastro de alunos em uma universidade. Nele, nós temos as classes: Pessoa; Aluno; Professor; Endereço; Turma; Disciplina; Curso; As classes têm os seguintes atributos: Uma Pessoa tem nome, email, telefone; Um Aluno tem matricula; Um Professor tem título; Cada Turma tem um nome; Cada Disciplina tem um nome e uma ementa; Cada Curso tem um nome e uma descrição; As classes se relacionam da seguinte forma: A classe Pessoa, que tem um Endereço, é a classe base para as classes Aluno e Professor; Um Aluno pode estar em várias Turmas; Cada Turma pode ter vários Alunos e apenas um Professor; Em uma Turma é lecionada uma Disciplina; Uma Disciplina pode ser lecionada em várias Turmas e é de um único Curso; Em Curso pode ser composto por várias Disciplinas; Baseados em todas esta informações, criamos um modelo de classes ilustrado no diagrama de classes a seguir: 32 Figura 2: Diagrama de Classes da Universidade 4.3 – Definido as Tabelas do Banco de Dados Figura 3: Diagrama de Tabelas do Banco de Dados (DED) Como se pode perceber, o banco de dados criado para o modelo não apresenta quaisquer características especiais, sendo modelado o mais simples e convencional possível, adaptado para comportar um modelo de classes. A seguir, 33 a listagem do script para criação do banco de dados no MySQL. Para executá-lo utilize o MySQL Query Browser. Primeiro abre o MySQL Query Browser, crie um banco de dados chamado “hibernate”. Depois de feito isto, ative o banco e digite o script de banco, e escolha “Execute”; CREATE TABLE Pessoa ( id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, nome VARCHAR(255) NULL, email VARCHAR(255) NULL, telefone VARCHAR(255) NULL, PRIMARY KEY(id) ); CREATE TABLE Curso ( id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, nome VARCHAR(255) NULL, descricao TEXT NULL, PRIMARY KEY(id) ); CREATE TABLE Professor ( Pessoa_id INTEGER UNSIGNED NOT NULL, titulo VARCHAR(255) NULL, PRIMARY KEY(Pessoa_id), INDEX Professor_FKIndex1(Pessoa_id), FOREIGN KEY(Pessoa_id) REFERENCES Pessoa(id) ON DELETE NO ACTION ON UPDATE NO ACTION ); CREATE TABLE Endereco ( Pessoa_id INTEGER UNSIGNED NOT NULL, rua VARCHAR(255) NULL, numero INTEGER UNSIGNED NULL, bairro VARCHAR(255) NULL, estado VARCHAR(255) NULL, complemento TEXT NULL, cep VARCHAR(255) NULL, cidade VARCHAR(255) NULL, PRIMARY KEY(Pessoa_id), INDEX Endereco_FKIndex1(Pessoa_id), FOREIGN KEY(Pessoa_id) REFERENCES Pessoa(id) ON DELETE NO ACTION ON UPDATE NO ACTION ); Listagem 1: Script de Criação do Banco de Dados 34 CREATE TABLE Aluno ( Pessoa_id INTEGER UNSIGNED NOT NULL, matricula VARCHAR(255) NULL, PRIMARY KEY(Pessoa_id), INDEX Aluno_FKIndex1(Pessoa_id), FOREIGN KEY(Pessoa_id) REFERENCES Pessoa(id) ON DELETE NO ACTION ON UPDATE NO ACTION ); CREATE TABLE Disciplina ( id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, Curso_id INTEGER UNSIGNED NOT NULL, nome VARCHAR(255) NULL, ementa TEXT NULL, PRIMARY KEY(id), INDEX Disciplina_FKIndex1(Curso_id), FOREIGN KEY(Curso_id) REFERENCES Curso(id) ON DELETE NO ACTION ON UPDATE NO ACTION ); CREATE TABLE Turma ( id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, Disciplina_id INTEGER UNSIGNED NOT NULL, Professor_Pessoa_id INTEGER UNSIGNED NOT NULL, nome VARCHAR(255) NULL, PRIMARY KEY(id), INDEX Turma_FKIndex1(Professor_Pessoa_id), INDEX Turma_FKIndex2(Disciplina_id), FOREIGN KEY(Professor_Pessoa_id) REFERENCES Professor(Pessoa_id) ON DELETE NO ACTION ON UPDATE NO ACTION, FOREIGN KEY(Disciplina_id) REFERENCES Disciplina(id) ON DELETE NO ACTION ON UPDATE NO ACTION ); CREATE TABLE Turma_has_Aluno ( Turma_id INTEGER UNSIGNED NOT NULL, Aluno_Pessoa_id INTEGER UNSIGNED NOT NULL, PRIMARY KEY(Turma_id, Aluno_Pessoa_id), INDEX Turma_has_Aluno_FKIndex1(Turma_id), INDEX Turma_has_Aluno_FKIndex2(Aluno_Pessoa_id), FOREIGN KEY(Turma_id) REFERENCES Turma(id) Como você já ter percebido, não há nada demais com esse modelo ON DELETE NOdeve ACTION ON de UPDATE NO as ACTION, de banco dados, tabelas estão interligadas normalmente usando chaves FOREIGN KEY(Aluno_Pessoa_id) REFERENCES Aluno(Pessoa_id) primárias e estrangeiras e uma tabela de relacionamento, no caso da relação N:N ON DELETE NO ACTION ON UPDATE NO As ACTION entre Aluno e Turma. tabelas têm as mesmas propriedades das classes e os ); mesmos relacionamentos. Listagem 1: Script de Criação do Banco de Dados (continuação) 35 Quando estiver mapeando as suas classes do modelo para o banco de dados ou vice versa, tente usar os mesmos nomes das classes e de suas propriedades, isso vai evitar várias dores de cabeça, como não saber se o nome “daquela” propriedade é todo em maiúsculas, ou se tem um “_” entre um nome e outro. Além do que, com veremos a seguir, se os nomes forem iguais, você não precisará repetir os nomes das tabelas e dos relacionamentos no mapeamento do Hibernate. 4.4 – Mapeamento das Tabelas para as Classes O próximo passo é a criação dos arquivos XML de mapeamento. O arquivo é identificado pelo nome da classe mais a extensão .hbm.xml. A criação/edição desses arquivos pode ser feita manualmente mas também poder ser feita utilizando uma ferramenta própria para este fim. Neste tutorial, porém nós não utilizaremos nenhuma ferramenta específica para este fim. Caso deseje utilizar algumas destas ferramentas descrevemos a seguir uma lista das mais comuns: HiberClipse - É um plugin para o Eclipse que permite a geração de arquivos de mapeamento Hibernate de uma conexão com o banco de dados; HibernateSynchronizer – É um plugin para o Eclipse totalmente feito em Java, que se associa aos arquivos *.hbm e automaticamente gera suas classes de domínio quando se arquivo de configuração de esquema (schema) do Hibernate é modificado; Hibernator – É um plugin para o Eclipse totalmente feito em Java, que permite a sincronização de uma classe em Java e um arquivo de mapeamento Hibernate. O usuário pode ter alterado a classe de domínio e não ter atualizado o mapeamento. Middlegen – É um mecanismo gerador de código orientado a banco de dados gratuito de propósito geral baseado em JDBC, Velocity, Ant and Xdoclet; Hibernate Tools – É um plugin para o Eclipse totalmente feito em Java, que permite a sincronização de uma classe em Java e um arquivo de mapeamento Hibernate; 36 Além destas, com certeza existem várias outras ferramentas criadas com esta finalidade, se desejar mais informação sobre este tipo de ferramenta, nós recomendamos pesquisar na Internet. Maiores detalhes sobre isto fogem ao foco deste tutorial, que não pretendemos prender a nenhuma ferramenta e ao contrário, permitir que cada um utiliza-o do modo que acreditar se mais cômodo. Independente se os arquivos forem gerados ou criados manualmente, podemos derivar novos arquivos apenas realizando as modificações necessárias sobre a primeira versão. Por exemplo, se iniciarmos um novo projeto e estivermos utilizando as classes herdadas de Pessoa e Endereço do nosso modelo, reutilizaremos boa parte da estrutura dos arquivos Pessoa.hbm.xml e Endereco.hbm.xml para o novo mapeamento. 4.4.1 – Conceito de Identidade Antes de começar a fazer os mapeamentos do Hibernate, temos um conceito do banco de dados que precisa ser revisto, a identidade. Para um banco de dados, o modo de diferenciar uma linha das outras, é usando chaves primárias, de preferência chaves não naturais, como as colunas “id” que nós criamos para nossas tabelas, mas em nosso modelo orientado a objetos, a identidade não é encontrada dessa forma. Em Java, nós definimos a identidade dos objetos sobrescrevendo o método “Object.equals (Object)”, do modo que nos convier. A implementação “default” deste método, define a identidade através da posição de memória ocupada pelo objeto. Não podemos usar o método “equals()” no banco de dados, porque ele não sabe que temos objetos, ele só entende tabelas, chaves primárias e estrangeiras. A solução é adicionar aos nossos objetos um identificador não natural, como os que nós encontramos no banco de dados, porque assim o banco de dados e o próprio Hibernate vão ser capazes de diferenciar os objetos e montar os seus relacionamentos. Nós fazemos isso adicionando uma propriedade chamada “id” do tipo Integer a todas as nossas classes, como no exemplo de código a seguir: 37 //Listagem do arquivo Pessoa.java public class Pessoa { private String nome; private String email; private String telefone; private Endereco endereco; private Integer id; //métodos getters e setters das propriedades } Listagem 2: Listagem simplificada da classe Pessoa Poderíamos ter escolhido o tipo primitivo “int” para a propriedade, mas trabalhando com objetos, o mapeamento e a resolução se um objeto existe ou não no banco de dados torna-se mais simples para o Hibernate. Outra observação importante são os métodos “getters” e “setters” para as propriedades dos objetos. O Hibernate não precisa de que as propriedades sejam definidas usando a nomenclatura dos JavaBeans, mas isso já é uma boa prática comum na comunidade, além de existirem outros frameworks e tecnologias que exigem essa nomenclatura (como a Expression Language dos JSPs), então nós definimos métodos “getters” e “setters” para todas as propriedades nos nossos objetos. 4.5 – Mapeamento de um Relacionamento 1:1 O primeiro mapeamento abordado é o mapeamento da classe Pessoa e do seu relacionamento com a classe Endereço. No nosso modelo, um Endereço tem apenas uma Pessoa, e uma Pessoa tem apenas um Endereço. Na listagem 2, podemos conferir o arquivo Pessoa.hbm.xml que faz o mapeamento para a classe Pessoa. 38 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="Pessoa"> <!-- Identificador da classe --> <id name="id"> <generator class="increment"/> </id> <!-- Propriedades da classe --> <property name="nome"/> <property name="telefone"/> <property name="email"/> <!-- Relacionamento da classe --> <one-to-one name="endereco" class="Endereco" cascade="save-update"/> </class> </hibernate-mapping> Listagem 2: Pessoa.hbm.xml - Mapeamento da classe Pessoa O arquivo de mapeamento é um arquivo XML que define as propriedades e os relacionamentos de uma classe para o Hibernate, este arquivo pode conter classes, classes componentes e queries em HQL ou em SQL. No nosso exemplo, temos apenas uma classe sendo mapeada no arquivo, a classe Pessoa. O arquivo XML começa normalmente com as definições da DTD e do nó raiz, o <hibernate-mapping>, depois vem o nó que nos interessa neste caso, <class>. No nó <class> nós definimos a classe que está sendo mapeada e para qual tabela ela vai ser mapeada. O único atributo obrigatório deste nó é “name”, que deve conter o nome completo da classe (com o pacote, se ele não tiver sido definido no atributo “package” do nó <hibernate-mapping>), se o nome da classe for diferente do nome da tabela, você pode colocar o nome da tabela no atributo “table”, no nosso exemplo isso não é necessário. 39 Seguindo em frente no nosso exemplo, temos o nó <id> que é o identificador dessa classe no banco de dados. Neste nó nós definimos a propriedade que guarda o identificador do objeto no atributo “name”, que no nosso caso é “id”, se o nome da coluna no banco de dados fosse diferente da propriedade do objeto, ela poderia ter sido definida no atributo “column”. Ainda dentro deste nó, nós encontramos mais um nó, o <generator>, este nó guarda a informação de como os identificadores (as chaves do banco de dados) são gerados, existem diversas classes de geradores, que são definidas no atributo “class” do nó, no nosso caso o gerador usado é o “increment”, que incrementa um ao valor da chave sempre que insere um novo objeto no banco, esse gerador costuma funcionar normalmente em todos os bancos. Os próximos nós do arquivo são os <property> que indicam propriedades simples dos nossos objetos, como Strings, os tipos primitivos (e seus wrappers), objetos Date, Calendar, Locale, Currency e outros. Neste nó, os atributos mais importantes são “name”, que define o nome da propriedade, “column” para quando a propriedade não tiver o mesmo nome da coluna na tabela e “type” para definir o tipo do objeto que a propriedade guarda. Normalmente, o próprio Hibernate é capaz de descobrir qual é o tipo de objeto que a propriedade guarda, não sendo necessário escrever isso no arquivo de configuração, ele também usa o mesmo nome da propriedade para acessar a coluna, se o atributo não tiver sido preenchido. Nós definimos as três propriedades simples da nossa classe, “nome”, “email” e “telefone”. O último nó do arquivo, <one-to-one>, define o relacionamento de 1-para-1 que a classe Pessoa tem com a classe Endereço. Este nó tem os atributos “name”, que define o nome da propriedade no objeto (neste caso, “endereco”), “type” que define a classe da propriedade e “cascade” que define como o Hibernate deve “cascatear” as ações feitas nesta classe para a classe relacionada, como atualizações, inserções e exclusões de registro. No nosso caso o “cascade” foi escolhido como “save-update” para que quando uma classe pessoa for inserida ou atualizada no banco, a propriedade “endereco” também seja inserida ou atualizada. Com isso, terminamos o mapeamento da nossa classe Pessoa, mas este mapeamento faz referência a uma outra classe o Hibernate ainda não conhece, a 40 classe Endereco. Vejamos então como fica o mapeamento desta classe, neste caso, o arquivo é o “Endereco.hbm.xml”: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="Endereco"> <id name="id" column="Pessoa_id"> <generator class="foreign"> <param name="property">pessoa</param> </generator> </id> <property <property <property <property <property <property <property name="bairro"/> name="cidade"/> name="complemento"/> name="estado"/> name="numero"/> name="rua"/> name="cep"/> <one-to-one name="pessoa" class="Pessoa" constrained="true"/> </class> </hibernate-mapping> Listagem 3: Endereco.hbm.xml - Mapeamento da classe Endereco Como você agora já conhece a estrutura básica do arquivo de mapeamento, vejamos as diferenças deste arquivo para o arquivo “Pessoa.hbm.xml”. A primeira coisa que você deve estar notando, é que o atributo “class” do nó <generator> não é “increment”, é “foreign” e agora também temos um corpo do nó, com o valor “pessoa”. Isso acontece devido ao tipo de relacionamento que nós criamos no banco de dados, entre as tabelas Endereco e Pessoa. Também existe mais um nó dentro de <generator> que não existia no mapeamento anterior, o <param>, esse nó serve para passar um parâmetro para a classe geradora do identificador, que neste caso é o nome da propriedade “dona” deste objeto, “pessoa”. 41 Para garantir que cada Endereco, pertença a apenas uma Pessoa, nós fizemos com que a chave primária de Endereco (Pessoa_id) fosse também a chave estrangeira que a liga a Pessoa, desse modo, garantimos que cada Endereco pertence a apenas uma Pessoa e vice-versa. Outra novidade é que também tivemos que colocar o nome da coluna da chave já que ela não é igual a o nome da propriedade da classe, a propriedade se chama “id” e a coluna “Pessoa_id”, assim, tivemos que declarar o atributo “column” do nó <id>. Continuando no mapeamento, encontramos as propriedades simples da classe, que são declaradas de modo idêntico as que nós vimos no mapeamento anterior, com o nó <property> e com o atributo “name” contendo o nome da propriedade. Mais uma vez, nós não indicamos o nome da coluna (porque os nomes são iguais as propriedades) nem o tipo, já que o Hibernate pode descobrir isso sozinho. No nosso modelo, o relacionamento entre Pessoa e Endereco é bidirecional, o que quer dizer que é sempre possível navegar de um objeto para o outro, pois Pessoa aponta para Endereco e Endereço aponta para Pessoa. Para simbolizar isso no mapeamento, nós temos que adicionar o nó que também existe no mapeamento de Pessoa, <one-to-one>. Como você já percebeu, os atributos continuam os mesmos, “name” como sendo o nome da propriedade na classe e “class” como sendo o nome da classe dessa propriedade. Mas nós temos um atributo que não existia na relação anterior, “constrained”, que nessa relação vai significar que existe uma relação entre a chave primária de Endereco e de Pessoa, avisando ao Hibernate que um Endereco não pode existir sem que exista uma Pessoa, assim, mesmo que o banco de dados não garantisse a integridade referencial do sistema, o próprio Hibernate garantiria. 4.6 – Mapeamento de Herança Continuando o trabalho de mapear o nosso modelo para o Hibernate, vamos para a herança que nós encontramos entre Pessoa, Aluno e Professor. Pessoa é a classe pai, da qual Aluno e Professor herdam as propriedades e comportamentos. 42 No Hibernate existem diversas formas de se fazer o mapeamento de uma relação de herança. Usando uma tabela para cada classe filha (a classe pai não teria tabela, as propriedades comuns se repetiriam nas tabelas filhas), usando uma tabela para todas as classes (discriminadores definiriam quando é uma classe ou outra), usando uma tabela para cada uma das classes (uma para a classe pai e mais uma para cada classe filha) ou até mesmo uma mistura de todas essas possibilidades (disponível apenas na versão 3 do Hibernate). Ficou confuso? Não se preocupe, neste artigo nós vamos abordar apenas a mais comum e mais simples de ser mantida, uma tabela para cada classe. No nosso banco de dados, temos uma tabela Pessoa e outras duas tabelas, Aluno e Professor, que estão relacionadas a professor través de suas chaves primárias a tabela Pessoa, garantindo assim que não existam Alunos ou Professores com o mesmo identificador. Vejamos então o mapeamento da classe Professor, o arquivo é o “Professor.hbm.xml”: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <joined-subclass name="Professor" extends="Pessoa"> <key column="Pessoa_id"/> <property name="titulo"/> <set name="turmas" inverse="true"> <key column="Pessoa_Professor_id"/> <one-to-many class="Turma"/> </set> </joined-subclass> </hibernate-mapping> Listagem 4: Professor.hbm.xml - Mapeamento da classe Professor Nesse mapeamento vemos um nó que nós ainda não conhecíamos, <joined-subclass>, que indica o mapeamento de herança usando uma tabela para cada classe, porque para retornar o objeto o Hibernate precisa fazer um “join” entre as duas tabelas. O atributo “name” é o nome da classe mapeada e o 43 atributo “extends” recebe o nome da classe pai (neste caso, Pessoa), se o nome da classe não fosse igual ao da tabela, poderíamos adicionar o nome da tabela no atributo “table” (que foi omitido porque o nome da classe é igual ao nome da tabela). Seguindo no mapeamento, percebemos mais um nó desconhecido, <key>. Este nó avisa ao Hibernate o nome da coluna que guarda a chave primária da tabela, esta coluna também é a chave estrangeira e que liga a tabela Professor a tabela Pessoa. Neste nó nós adicionamos o atributo “column” e colocamos o nome da coluna que guarda a chave, que é “Pessoa_id”. O mapeamento continua com a declaração de uma propriedade e com um nó que nós também não conhecemos, <set>, que pode simbolizar um relacionamento 1:N ou N:N. Este nó vai ser explicado mais tarde neste artigo. Passando para o mapeamento da classe Aluno, percebemos que não existe muita diferença, vejamos o arquivo “Aluno.hbm.xml”: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <joined-subclass name="Aluno" extends="Pessoa"> <key column="Pessoa_id"/> <property name="matricula"/> <set name="turmas" table="Turma_has_Aluno" inverse="false"> <key column="Aluno_Pessoa_id"/> <many-to-many class="Turma" column="Turma_id"/> </set> </joined-subclass> </hibernate-mapping> Listagem 5: Aluno.hbm.xml - Mapeamento da classe Aluno 44 A classe é declarada do mesmo modo que Professor, usando o nó <joinedsubclass> e usando os mesmos atributos que foram usados no mapeamento anterior. O nó <key> também foi incluído do mesmo modo, com o nome da coluna da chave primária/estrangeira da tabela, mais uma declaração de propriedade e mais um nó <set>, nada além do que já foi visto. 4.7 – Mapeamento de Associações 1:N e N:N Você já conheceu o nó <set> nos mapeamentos anteriores, vamos entender agora como ele funciona e como utilizá-lo para tornar o acesso aos objetos associados ainda mais simples, mais uma vez sem nenhuma linha de SQL. Um “set” ou “conjunto” representa uma coleção de objetos não repetidos, que podem ou não estar ordenados (dependendo da implementação escolhida da interface java.util.Set). Quando você adiciona um nó deste tipo em um arquivo de mapeamento, você está indicando ao Hibernate que o seu objeto tem um relacionamento 1:N ou N:N com outro objeto. Vejamos o exemplo da classe Professor, que tem um relacionamento 1:N com a classe Turma: <joined-subclass name="Professor" extends="Pessoa"> <key column="Pessoa_id"/> <property name="titulo"/> <set name="turmas" inverse="true"> <key column="Pessoa_Professor_id"/> <one-to-many class="Turma"/> </set> </joined-subclass> Listagem 6: Detalhe de Professor.hbm.xml - Mapeamento 1:N com a classe Turma No nó <set> o primeiro atributo a ser encontrado é “name” que assim como nos outros nós, define o nome da propriedade que está sendo tratada, já o outro atributo, “inverse”, define como o Hibernate vai tratar a inserção e retirada de objetos nessa associação. Quando um lado da associação define o atributo 45 “inverse” para “true” ele está indicando que apenas quando um objeto for inserido do “outro lado” da associação ele deve ser persistido no banco de dados. Para entender melhor o atributo “inverse” pense em como está definido o mapeamento da classe Professor. Lá ele está definido para “true”, significando que apenas quando uma Turma adicionar um professor, o relacionamento vai ser persistido no banco de dados. Em código: Professor professor = new Professor(); Turma turma = new Turma(); turma.setProfessor(professor); professor.getTurmas().add(turma); Listagem 7: Exemplo de código mostrando a importância de utilizar o “inverse” Se apenas o Professor adicionando a Turma a sua coleção de Turmas, nada ia acontecer ao banco de dados. Isso parece não ter muito sentido, mas se você prestar bem atenção, o Hibernate não tem como saber qual dos dois lados foi atualizado, desse modo ele vai sempre atualizar os dois lados duas vezes, uma para cada classe da relação, o que seria desnecessário. Usando “inverse” você define de qual lado o Hibernate deve esperar a atualização e ele vai fazer a atualização apenas uma vez. Lembre-se sempre de adicionar os objetos dos dois lados, pois em Java os relacionamentos não são naturalmente bidirecionais. Voltando a o nó <set>, percebemos que dentro dele ainda existem mais dois outros nós, <key> e <one-tomany>. O nó <key> representa a coluna da tabela relacionada (neste caso, Turma) que guarda a chave estrangeira para a classe Professor, nós adicionamos o atributo “column” e o valor é o nome da coluna, que neste caso é “Pessoa_Professor_id”. No outro nó, <one-to-many>, nós definimos a classe a qual pertence essa coleção de objetos, que é Turma (lembre-se sempre de usar o nome completo da classe, com o pacote). Depois do relacionamento um-para-muitos (1:N) vejamos agora como mapear um relacionamento muitos-para-muitos (N:N). Em um banco de dados relacional, este tipo de associação faz uso de uma “tabela de relação”, que guarda as chaves estrangeiras das duas tabelas associadas, como ocorre no nosso exemplo, onde tivemos que criar uma tabela “Turma_has_Aluno”, para 46 poder mapear o relacionamento N:N entre as classes Turma e Aluno no banco de dados. Como você já sabe, este tipo de associação também é definida usando o nó <set>, vejamos então o nosso exemplo: <joined-subclass name="Aluno" extends="Pessoa"> <key column="Pessoa_id"/> <property name="matricula"/> <set name="turmas" table="Turma_has_Aluno" inverse="false"> <key column="Aluno_Pessoa_id"/> <many-to-many class="Turma" column="Turma_id"/> </set> </joined-subclass> Listagem 8: Exemplo de código mostrando a utilização de “set” Os atributos do nó <set> neste mapeamento são parecidos com os que nós vimos no mapeamento da classe Professor, mas eles contêm algumas informações adicionais graças ao tipo da associação. Primeiro, temos um novo atributo no nó <set>, o “table”. Como nós havíamos falado antes, para uma associação N:N você precisa de uma “tabela de relacionamento” e o atributo “table” guarda o nome desta tabela. Mais uma vez o atributo “inverse” foi marcado como “true” indicando que apenas as ações feitas do “outro lado” da associação vão ser persistidas no banco de dados. Dentro do nó, nós encontramos mais dois nós, <key>, que vai conter, no atributo “column”, o nome da coluna que guarda a chave estrangeira para a tabela Aluno e o nó <many-to-many>, que contém a classe dos objetos da coleção, no atributo “class” e o nome da coluna na “tabela de relacionamento” que referencia a chave primária da tabela Turma, no atributo “column”. Vamos agora terminar de mapear as nossas outras classes do nosso modelo e tornar as associações bidirecionais, como manda o nosso modelo de classes. Começando pela classe Turma (o arquivo “Turma.hbm.xml”): 47 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="Turma"> <id name="id"> <generator class="increment"/> </id> <property name="nome"/> <many-to-one name="professor" class="Professor" column="Professor_Pessoa_id"/> <many-to-one name="disciplina" class="Disciplina" column="Disciplina_id"/> <set name="alunos" table="Turma_has_Aluno"> <key column="Turma_id"/> <many-to-many class="Aluno" column="Aluno_Pessoa_id"/> </set> </class> <query name="buscarTurmasPeloNome"> <![CDATA[from Turma t where t.nome = :nome]]> </query> </hibernate-mapping> Listagem 9: Turma.hbm.xml - Mapeamento da classe Turma A maior parte do código já é conhecida, mas ainda existem algumas coisas que precisam ser esclarecidas. Turma é o lado “um” de dois relacionamentos umpara-muitos, um com a classe Professor e outro com a classe Disciplina, para tornar essa associação bidirecional nós vamos utilizar o nó <many-to-one>, que modela o lado “um” de uma associação um-para-muitos (1:N). Neste nó, nós inserimos os atributos “name”, que recebe o nome da propriedade, “class”, que 48 recebe o nome da classe da propriedade e “column”, que recebe o nome da coluna nesta tabela que guarda a chave estrangeira para a outra tabela do relacionamento. O resto do mapeamento é idêntico aos outros mapeamentos que nós já estudamos, a única diferença é o lado da associação que ele está modelando. Os dois outros mapeamentos, de Disciplina e Curso, não trazem nenhuma novidade para o nosso estudo, portanto esteja livre para estudar os arquivos junto com o resto do material de apoio. 4.8 – Configuração do Hibernate 3 A engine do Hibernate pode ser configurada de três modos diferentes, instanciando um objeto de configuração (org.hibernate.cfg.Configuration) e inserindo as suas propriedades programaticamente, usando um arquivo .properties com as suas configurações e indicando os arquivos de mapeamento programaticamente ou usando um arquivo XML (o “hibernate.cfg.xml”) com as propriedades de inicialização e os caminhos dos arquivos de mapeamento. Vejamos como configurar o Hibernate para o projeto: <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.dialect"> org.hibernate.dialect.MySQLDialect </property> <property name="hibernate.connection.driver_class"> com.mysql.jdbc.Driver </property> <property name="hibernate.connection.url"> jdbc:mysql://localhost/hibernate?autoReconnect=true </property> <property name="hibernate.connection.username"> root </property> Listagem 10: hibernate.cfg.xml – Configuração do HIbernate 49 <property name="hibernate.connection.password"> </property> <!-- Condiguração do c3p0 --> <property name="hibernate.c3p0.max_size">10</property> <property name="hibernate.c3p0.min_size">2</property> <property name="hibernate.c3p0.timeout">5000</property> <property name="hibernate.c3p0.max_statements">10</property> <property name="hibernate.c3p0.idle_test_period">3000</property> <property name="hibernate.c3p0.acquire_increment">2</property> <!-- Configurações de debug --> <property <property <property <property <mapping <mapping <mapping <mapping <mapping <mapping name="show_sql">true</property> name="use_outer_join">true</property> name="hibernate.generate_statistics">true</property> name="hibernate.use_sql_comments">true</property> <mapping resource="Curso.hbm.xml"/> resource="Disciplina.hbm.xml"/> resource="Turma.hbm.xml"/> resource="Pessoa.hbm.xml"/> resource="Aluno.hbm.xml"/> resource="Professor.hbm.xml"/> resource="Endereco.hbm.xml"/> </session-factory> </hibernate-configuration> Listagem 10: hibernate.cfg.xml – Configuração do Hibernate (continuação) Na documentação do Hibernate você pode verificar todas as opções de propriedades que podem ser utilizadas e seus respectivos resultados, por isso nós vamos nos ater ao que é importante para começarmos a trabalhar. Vejamos as propriedades: hibernate.dialect: é a implementação do dialeto SQL específico do banco de dados a ser utilizado. hibernate.connection.driver_class: é o nome da classe do driver JDBC do banco de dados que está sendo utilizado. hibernate.connection.url: é a URL de conexão específica do banco que está sendo utilizado. hibernate.connection.username: é o nome de usuário com o qual o Hibernate deve se conectar ao banco. 50 hibernate.connection.password: é a senha do usuário com o qual o Hibernate deve se conectar ao banco. Essa segunda parte do arquivo são as configurações do “pool” de conexões escolhido para a nossa aplicação. No nosso exemplo o pool utilizado é o C3P0, mas você poderia utilizar qualquer um dos pools que são oferecidos no Hibernate ou então usar um DataSource do seu servidor de aplicação. Na terceira parte, estão algumas opções para ajudar a debugar o comportamento do Hibernate, a propriedade “show_sql” faz com que todo o código SQL gerado seja escrito na saída default, “hibernate.generate_statistics” faz com que o Hibernate gere estatísticas de uso e possa diagnosticar uma má performance do sistema e “hibernate.use_sql_comments” adiciona comentários ao código SQL gerado, facilitando o entendimento das queries. A última parte do arquivo é onde nós indicamos os arquivos de mapeamento que o Hibernate deve processar antes de começar a trabalhar, se você esquecer de indicar um arquivo de mapeamento de qualquer classe, essa classe não vai poder ser persistida pela engine do Hibernate. Outro detalhe importante, é que quando você usa mapeamentos com Herança, o mapeamento pai deve sempre vir antes do filho. CAPÍTULO 5 – FUNCIONAMENTO DO HIBERNATE Após ter realizado a configuração do Hibernate, ele já está pronto para funcionar, tem-se de entender o mecanismo de persistência dele. Para o Hibernate, existem três tipos de objetos, objetos “transient” (transientes), “detached” (desligados) e “persistent” (persistentes). Objetos “transient” são aqueles que ainda não tem uma representação no banco de dados (ou que foram excluídos), eles ainda não estão sobre o controle do framework e podem não ser mais referenciáveis a qualquer momento, como qualquer objeto normal em Java. Objetos “detached” têm uma representação no banco de dados, mas não eles fazem mais parte de uma sessão do Hibernate, o que significa que o seu estado pode não estar mais sincronizado com o banco de dados. Objetos “persistent” são os objetos que tem uma representação no banco de dados e que ainda fazem parte de uma transação do Hibernate, garantindo assim que o seu estado esteja sincronizado com o banco de dados (nem sempre, claro). No Hibernate, assim como no JDBC, existem os conceitos de sessão e transação. Uma sessão é uma conexão aberta com o banco de dados, onde nós podemos executar queries, inserir, atualizar e deletar objetos, já a transação é a demarcação das ações, uma transação faz o controle do que acontece e pode fazer um roolback, assim como uma transação do JDBC, no caso de terem sido encontrados problemas. 52 Edite o arquivo de configuração do Hibernate (hibernate.cfg.xml) com as suas informações específicas (nome de usuário, senha, URL de conexão, etc), coloque ele na raiz do seu classpath, junto com as classes compiladas e os arquivos de mapeamento, porque nós vamos colocar o Hibernate pra funcionar (tenha certeza de que o seu classpath está configurado corretamente, com todos os arquivos necessários). Primeiro, vamos criar uma classe para configurar e abrir as sessões do Hibernate, o código é simples: /* * Criado em 04/05/2005 * * Código desenvolvido por Francisco Augusto Franco do Nascimento * */ import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; /** * @author Francisco * * Código desenvolvido por Francisco Augusto Franco do Nascimento * */ public class HibernateUtility { private static SessionFactory factory; static { //Bloco estático que inicializa o Hibernate try { factory = new Configuration().configure().buildSessionFactory(); } catch (Exception e) { e.printStackTrace(); factory = null; } } public static Session getSession() { //Retorna a sessão aberta return factory.openSession(); } } Listagem 11: HibernateUtility.java – Classe para configurar e abrir sessões do Hibernate 53 O bloco estático (as chaves marcadas como “static”) instancia um objeto de configuração do Hibernate (org.hibernate.cfg.Configuration), chama o método configure() (que lê o nosso arquivo hibernate.cfg.xml) e depois que ele está configurado, criamos uma SessionFactory, que é a classe que vai ficar responsável por abrir as sessões de trabalho do Hibernate. Faça um pequeno teste pra ter certeza de que tudo está funcionando: //Arquivo Teste1.java public class Teste1 { public static void main(String[] args) { Session sessao = HibernateUtility.getSession(); //Abrindo uma sessão Transaction transaction = sessao.beginTransaction(); //Iniciando uma transação Curso curso = new Curso(); //Instanciando um objeto transiente curso.setNome("Desenvolvimento de Software"); //Preenchendo as propriedades do objeto curso.setDescricao("Curso só pra programadores"); sessao.save(curso); //Transformando o objeto transiente em um objeto //persistente no banco de dados transaction.commit(); //Finalizando a transação sessao.close(); //Fechando a sessão } } Listagem 12: Teste1.java – Teste de utilização do Hibernate para persistência de uma instância da classe Curso Se ele não lançou nenhuma exceção, o seu ambiente está configurado corretamente. Vamos entender agora o que foi que aconteceu neste código, primeiro nós inicializamos a SessionFactory, dentro do bloco estático na classe HibernateUtility, depois que ela é inicializada, o método getSession() retorna uma nova sessão para o código dentro do main(). Após a sessão ter sido retornada, nós iniciamos uma transação, instanciamos um objeto Curso, preenchemos as suas propriedades e chamamos o método save() na sessão. Após o método 54 save(), finalizamos a transação e fechamos a sessão. Acabamos de inserir um registro no banco de dados sem escrever nenhuma linha de SQL, apenas com a chamada de um método. O código de aplicações que usam o Hibernate costuma mostrar esse mesmo comportamento, abrir sessão, iniciar transação, chamar os métodos save(), update(), get(), delete(), etc, fechar a transação e depois a sessão, um comportamento muito parecido com o de aplicações JDBC comuns, a diferença é que não escrevemos nem uma linha sequer de SQL para tanto. 5.1 – Realização de Pesquisas no Banco usando o Hibernate Agora que você já sabe mapear e configurar a fera, podemos passar para uma das partes mais importantes do funcionamento do framework, as pesquisas. Existem três meios de se fazer buscas usando o Hibernate, usando a sua linguagem própria de buscas, a Hibernate Query Language (HQL), usando a sua Criteria Query API (para montar buscas programaticamente) e usando SQL puro. A maioria das suas necessidades deve ser suprida com as duas primeiras alternativas, o resto, você sempre pode usar SQL pra resolver. A HQL é uma extensão da SQL com alguns adendos de orientação a objetos, nela você não vai referenciar tabelas, vai referenciar os seus objetos do modelo que foram mapeados para as tabelas do banco de dados. Além disso, por fazer pesquisas em objetos, você não precisa selecionar as “colunas” do banco de dados, um select assim: “select * from Turma” em HQL seria simplesmente “from Turma”, porque não estamos mais pensando em tabelas e sim em objetos. A Criteria Query API é um conjunto de classes para a montagem de queries em código Java, você define todas as propriedades da pesquisa chamando os métodos e avaliações das classes relacionadas. Como tudo é definido programaticamente, você ganha todas as funcionalidades inerentes da programação orientada a objetos para montarem as suas pesquisas e ainda garante a completa independência dos bancos de dados, pois a classe de “dialeto SQL” do seu banco vai se encarregar de traduzir tudo o que você utilizar. 55 Antes de ver os exemplos propriamente ditos, vejamos como criar um objeto que implemente a interface Query ou Criteria. Para instanciar um desses objetos, você vai ter que abrir uma sessão do Hibernate, como nós já vimos na listagem do arquivo “Teste1.java”. Vejamos como ficaria o código: //Arquivo Teste2.java public class Teste2 { public static void main(String[] args) { Session sessao = HibernateUtility.getSession(); //Abrindo uma sessão Transaction tx = sessao.beginTransaction(); //Iniciando uma transação Query select = sessao.createQuery("from Turma"); List objetos = select.list(); System.out.println(objetos); //Transformando o objeto transiente em um objeto //persistente no banco de dados tx.commit(); //Finalizando a transação sessao.close(); //Fechando a sessão } } Listagem 13: Teste2.java – Teste para consultar dados de uma Turma usando HQL O código ainda segue aquela mesma seqüência da listagem anterior, abrir uma sessão, iniciar uma transação e começar a acessar o banco de dados. Para criar uma query, nós chamamos o método createQuery(String query) na sessão, passando como parâmetro o String que representa a query. Depois disso, podemos chamar o método list(), que retorna um “List” com os objetos resultantes da query. Usando a Criteria API, o código ficaria desse modo: 56 //Arquivo Teste3.java public class Teste3 { public static void main(String[] args) { Session sessao = HibernateUtility.getSession(); //Abrindo uma sessão Transaction tx = sessao.beginTransaction(); //Iniciando uma transação Criteria select = sessao.createCriteria(Turma.class); List objetos = select.list(); System.out.println(objetos); //Transformando o objeto transiente em um objeto //persistente no banco de dados tx.commit(); //Finalizando a transação sessao.close(); //Fechando a sessão } } Listagem 14: Teste3.java – Teste para consultar dados de uma Turma usando Criteria API Veja que apenas a linha onde nós criávamos a query mudou. Agora, em vez de chamar o método createQuery(String query), nós chamamos o método createCriteria(Class clazz), passando como parâmetro a classe que vai ser pesquisada, que no nosso caso é Tuma. Outro complemento importante do Hibernate é o suporte a paginação de resultados. Para paginar os resultados, nós chamamos os métodos setFirstResult(int first) e setMaxResults(int max). O primeiro método indica de qual posição os objetos devem começar a serem carregados e o segundo indica o máximo de objetos que devem ser carregados. Estes métodos estão presentes tanto na interface “Query” quanto na interface “Criteria”. Vejamos como nós deveríamos proceder para carregar apenas as dez primeiras turmas do nosso banco de dados: Session sessao = HibernateUtility.getSession(); Transaction tx = sessao.beginTransaction(); Criteria select = sessao.createCriteria(Turma.class); select.setFirstResult(0); select.setMaxResults(10); List objetos = select.list(); System.out.println(objetos); tx.commit(); sessao.close(); Listagem 15: Trecho para consultar as 10 primeiras turmas 57 Veja que nós chamamos os métodos setFirstResult() e setMaxResults() antes de listar os objetos da query e lembre-se também que a contagem de resultados (assim como os arrays) começa em zero, não em um. Uma propriedade específica da HQL (que foi herdada do JDBC), é o uso de parâmetros nas queries. Assim como no JDBC, os parâmetros podem ser numerados, mas também podem ser nomeados, o que se simplifica ainda mais o uso e a manutenção das queries, porque a troca de posição não vai afetar o código que as usa. Vejamos um exemplo do uso de parâmetros: Session sessao = HibernateUtility.getSession(); Transaction tx = sessao.beginTransaction(); Query select = sessao.createQuery("from Turma as turma where turma.nome = :nome"); select.setString("nome", "Jornalismo"); List objetos = select.list(); System.out.println(objetos); tx.commit(); sessao.close(); Listagem 16: Trecho para consultar turmas usando parametro nomeado Nesse nosso novo exemplo, tivermos mais algumas adições a query HQL. A primeira é o uso do “as”, que serve para “apelidar” uma classe no na nossa expressão (do mesmo modo do “as” em SQL), ele não é obrigatório, o código poderia estar “from Turma turma ...” e ele funcionaria normalmente. Outra coisa a se notar, é o acesso as propriedades usando o operador “.” (ponto), você sempre acessa as propriedades usando esse operador. E a última parte é o parâmetro propriamente dito, que deve ser iniciado com “:” (dois pontos) para que o Hibernate saiba que isso é um parâmetro que vai ser inserido na query. Insere-se um parâmetro nomeado usando o método “set” correspondente ao tipo de parâmetro, passando primeiro o nome com o qual ele foi inserido e depois o valor que deve ser colocado. E se você também não gosta de ficar metendo código SQL no meio do seu programa, porque deveria meter código HQL? O Hibernate facilita a externalização de queries HQL (e até SQL) com os nós <query> e <sql-query> 58 nos arquivos de mapeamento. Vamos adicionar uma query no mapeamento da classe turma (o arquivo “Turma.hbm.xml”): <query name="buscarTurmasPeloNome"> <![CDATA[from Turma t where t.nome = :nome]]> </query> Listagem 17: Trecho mostrando a criação de consultas externas nomeadas com “query” Essas linhas adicionadas no arquivo de mapeamento vão instruir o Hibernate a criar um PreparedStatement para esse select, fazendo com que ele execute mais rápido e possa ser chamado de qualquer lugar do sistema. Você deve preencher o atributo “name” com o nome pelo qual esta query deve ser chamada na aplicação e no corpo do nó você deve colocar o código da query, de preferência dentro de uma tag CDATA, pra evitar que o parser XML entenda o que está escrito na query como informações para ele. Veja como executar uma “named query”: Session sessao = HibernateUtility.getSession(); Transaction tx = sessao.beginTransaction(); Query select = sessao.getNamedQuery("buscarTurmasPeloNome"); select.setString("nome", "Jornalismo"); List objetos = select.list(); System.out.println(objetos); tx.commit(); sessao.close(); Listagem 18: Trecho mostrando a utilização de consultas externas nomeadas A única diferença para as outras listagens é que em vez de escrevermos a query dentro do código Java, ela ficou externalizada no arquivo de mapeamento. Para instanciar o objeto, chamamos o método getNamedQuery(String name), passando como parâmetro o nome da query definido no atributo “name” no arquivo XML. 59 5.2 – Restrições as Pesquisas Nesta seção, iremos entender como adicionar restrições as nossas pesquisas no Hibernate. Os exemplos vão seguir uma dinâmica simples, com uma versão em HQL e como o mesmo exemplo poderia ser feito usando a API de Criteria. A HQL e a API de Criteria suportam todos os operadores de comparação da SQL (<, >, <>, <=, >=, =,between, not between, in e not in), vejamos um exemplo em HQL: from Aluno al where al.endereco.numero >= 50 O uso dos operadores de comparação segue a mesma sintaxe da linguagem SQL, usando Criteria, essa mesma query poderia ser expressa da seguinte maneira: Criteria select = sessao.createCriteria(Aluno.class); select.createCriteria("endereco").add( Restrictions.get( "numero", new Integer(10) ) ); Nós criamos a o objeto Criteria usando a classe Aluno, mas a nossa restrição é para a propriedade “numero” da classe Endereco que está associada a classe Aluno, então temos que “descer” um nível, para poder aplicar a restrição a propriedade “numero” de “endereco”. Usando Criteria, você pode usar os métodos estáticos da classe “org.hibernate.criterion.Restrictions” para montar expressões. Os operadores lógicos (e os parênteses) também estão a sua disposição e em HQL eles continuam com a mesma sintaxe do SQL. Você pode usar or, and e parênteses para agrupar as suas expressões. Vejamos um exemplo: from Endereco end where ( end.rua in (“Epitácio Pessoa”, “Ipiranga”) ) or ( end.numero between 1 and 100 ) Passando para Criteria: Criteria select = sessao.createCriteria(Endereco.class); String[] ruas = {"Epitácio Pessoa", "Ipiranga"}; select.add( Restrictions.or( Restrictions.between("numero", new Integer(1), new Integer(100) ), 60 Restrictions.in( "rua", ruas ) ) ); O código usando Criteria é muito mais longo do que a expressão em HQL e também é menos legível. Procure utilizar Criteria apenas quando for uma busca que está sendo montada em tempo de execução, se você pode prever a busca, use “named queries” e parâmetros para deixar o eu código mais limpo e mais simples. Outra parte importante das pesquisas são as funções de pesquisa em Strings. Você pode usar “LIKE” e os símbolos “%” e “_”, do mesmo modo que eles são utilizados em SQL. Vejamos um exemplo: from Curso c where c.nome like “Desenvolvimento%” Em Criteria: Criteria select = sessao.createCriteria(Curso.class); select.add( Restrictions.like("nome", "Desenvolvimento", MatchMode.START) ); Além disso, pode-se ainda ordenar as suas pesquisas, em ordem ascendente ou descendente. Como nos exemplos a seguir: from Aluno al order by al.nome asc Em Criteria: Criteria select = sessao.createCriteria(Aluno.class); select.addOrder( Order.asc("nome") ); CAPÍTULO 6 – CRIANDO UMA APLICAÇÃO COMPLETA USANDO O HIBERNATE Agora que já analisamos individualmente cada recurso do Hibernate vamos partir para a criação de uma aplicação simples, que vai demonstração toda a facilidade de utilização da ferramenta. Vamos necessitar do ambiente Eclipse para desenvolver esta aplicação, por isto é bom já estar familiarizado com ele e enterder como o utilizá-lo. Bem, pode ser utilizada qualquer outra ferramenta, basta converter os procedimentos específicos do Eclipse para ela. O primeiro passo é a criação de Java Project (Projeto Java). Para tal siga os seguintes passos: 1. Escolha “File” / “New” / “Project”; 2. Vai abrir um Wizard chamado “New Project”, nele voçê irá selecionar “Java Project” e depois clique em “Next >”; 3. O nome da nossa aplicação será Universidade, então em Project Name, digite “Universidade”. Clique em “Create new project in workspace” e em seguida em “Next >”; 4. Após isto, aparecerá a tela “Java Settings”, nela iremos personalizar as opções do projeto java. O importante no momento é incluir no projeto os pacotes da biblioteca do Hibernate e da biblioteca do 62 Connector/J para acesso ao MySQL. Selecione “Libraries” / “Add External JARs”; 5. Para adicionar o suporte ao Hibernate e assumindo que foi ele foi instalado na pasta “C:\Hibernate-3.0”, adicione o arquivo “hibernate3.jar” que está na raiz da pasta. Selecione “Libraries” / “Add External JARs” novamente e incluia todos os arquivos “.jar” da pasta “lib” do Hibernate; 6. Para adicionar o suporte ao Connector/J e assumindo que foi ele foi instalado na pasta “C:\JDBC”, adicione o arquivo “mysql-connectorjava-3.1.7-bin.jar” que está na raiz da pasta; 7. Selecione “Finish” e pronto! Agora podemos começar o nosso projeto; Já que temos um novo projeto no Eclipse, temos que colocar na mesma pasta do projeto todos os arquivos de mapeamento XML das classes. Para tal, primeiro temos que ter certeza onde está à pasta criada para o projeto (criada na workspace atual). Clique com o botão direito no “Package Explorer” em “Universidade”, depois em “Properties”. Na tela que aparece clique em “Info” e encontre o campo “Location”. Ele diz qual é o caminho da pasta onde está o projeto Java e onde ficarão os códigosfontes em java. Como nós já criamos alguns arquivos XML, recomendamos que eles sejam copiados para esta pasta. Então, copie para ela os arquivos Pessoa.hbm.xml, , Endereco.hbm.xml, Professor.hbm.xml, Aluno.hbm.xml, Turma.hbm.xml. Para completar o conjunto de classes de domínio apresentamos a seguir as listagens dos arquivos de mapeamento das classes Curso (Curso.hbm.xml) e Disciplina (Disciplina.hbm.xml). 63 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="Curso"> <id name="id"> <generator class="increment"/> </id> <property name="nome"/> <property name="descricao"/> <set name="disciplinas" inverse="true" cascade="save-update"> <key column="Curso_id"/> <one-to-many class="Disciplina"/> </set> </class> </hibernate-mapping> Listagem 19: Curso.hbm.xml - Mapeamento da classe Curso 64 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="Disciplina"> <id name="id"> <generator class="increment"/> </id> <property name="ementa"/> <property name="nome"/> <set name="turmas" inverse="true"> <key column="Disciplina_id"/> <one-to-many class="Turma"/> </set> <many-to-one name="curso" class="Curso" column="Curso_id"/> </class> </hibernate-mapping> Listagem 20: Disciplina.hbm.xml - Mapeamento da classe Disciplina Já com os arquivos de mapeamento prontos, precisamos criar as classes em Java que irão ser mapeadas. Neste caso utilizamos para fazer isto o Jude, uma ferramenta muito útil que é capaz de criar 9 dos 13 diagramas da UML, além de fácil utilização e possuir uma versão gratuita. Para criá-los siga os seguintes passos: Inicie o Jude. Clique “Diagram / Class Diagram”; Aparecerá uma tela em branco com uma barra de ferramentas em cima. Crie uma diagrama de classe como na figura 2 (mostrada anteriormente). Clique em “Tools / Export Java” na barra de menu. 65 No quadro de diálogo encontra a pasta do projeto e clique em “Open”; O Jude irá criar os arquivos .java das classes de domínio nesta pasta; Normalmente para as classes do domínio não se criam os métodos de acesso nos modelo. Para isto o Eclipse poder criá-los todos, automaticamente, para isto abra cada arquivo .java e clicando com o botão direito selecione em “Source / Generation Getter and Setter method”. Alternativamente poder-se criar cada arquivo individualmente através de qualquer outro método. A listagem dos arquivos encontra-se no Apêndice A. Para conficurar o Hibernate, copie para esta pasta o arquivo hibernate.cfg.xml criado anteriormente. Além das classes de domínio descritas na figura 2, nós vamos precisar da classe HibernateUtility que criamos anteriormente, e mais algumas classes de controle para demonstrar as funcionalidades básicas do Hibernate. A primeira a ser criada será chamada de AplicacaoUniversidade, e irá ser o ponto de entrada (o “main()”). Crie uma classe chamada AplicacaoUniversidade. Faça isto através dos seguintes passos: 1. Escolha “File” / “New” / “Class”; 2. Vai abrir um Wizard chamado “New Java Class”. O nome da nossa nova classe será AplicacaoUniversidade, então em Name, digite “AplicacaoUniversidade”. Na opção “Which method stubs would you like to create?”, deixe apenas a opção “public static void main(String[] args)” marcada e em seguida em “Next >”; O código fonte para a classe AplicacaoUniversidade é: 66 public class AplicacaoUniversidade { /** * @param args */ public static void main(String[] args) { String saida = null; Leitor leitor = new Leitor(System.in); do { System.out.println("============================" + "=============="); System.out.println("=== Universidade - Tutorial Hibernate ==="); Listagem 21: AplicacaoUniversidade.java – Ponto de entrada 67 System.out.println("=========================================="); System.out.println("1 - Listar todos os cursos"); System.out.println("2 - Listar todas as disciplinas"); System.out.println("3 - Listar todos os cursos e suas disciplinas"); System.out.println("4 - Cadastrar novo curso"); System.out.println("5 - Cadastrar nova disciplina"); System.out.println("6 - Remove disciplina"); System.out.println("7 - Altera o nome do curso"); System.out.println("8 - Cadastrar novo aluno"); System.out.println("9 - Listar todos os alunos"); System.out.println("S - Sair"); saida = leitor.leDoTeclado("Digite a sua opção:"); if (saida.equalsIgnoreCase("s")) break; try { int opcao = Integer.parseInt(saida); switch (opcao) { case 1: new ListaCursos(false); break; case 4: new NovoCurso(); break; default: throw new NumberFormatException(); } } catch (Exception e) { System.out.println("Opção inválida!"); } } while (true); System.out.println("==================================" + "========="); System.out.println("=== Universidade - Tutorial " + "Hibernate ===="); System.out.println("==================================" + "========="); System.out.println("(C) 2005 - Criado por Francisco Augusto"); } } Listagem 21: AplicacaoUniversidade.java – Ponto de entrada (continuação) 6.1 – Descrevendo o código da classe AplicacaoUniversidade Poderíamos fazer utilizando o pacote Swing, com ele iríamos fizer com uma interface com o usuário bem melhor, e muito mais intuitiva, porém iríamos adicionar uma complexidade que não é conveniente neste momento. 68 Na listagem 22 podemos visualizar a classe AplicacaoUniversidade. Nesta classe temos o ponto de entrada da nossa aplicação de demonstração. Ele está utilizando o console, já que se fossemos utilizar os recurso de GUI, iríamos adicionar muita complexidade desnecessária. O importante é entender como o Hibernate funciona e como utilizá-lo com eficiência. Na linha 6, declaramos o método main e iniciamos a aplicação propriamente dita. Definimos uma variável local chamada saída, ela irá contém a opção selecionada pelo usuário através do menu. Criamos uma instância da classe Leitor passando como parâmetro do construtor a entrada padrão do console (System.in) , ela é responsável por permitir que o usuário utilize o console para entrada de dados. Em seguida, iniciaremos um loop e mostraremos as opções do menu da tela do console (System.out). Aguardamos o usuário escolher uma opção e verificamos se é a opção de saida da aplicação, se for ela sai. Para simplificar, faremos uma conversão de String para int, assim poderemos realizar um parsing das opções com uma única instrução switch. Para cada caso, crie uma nova instância da classe de Controle. A partir deste popnto o controle será passado para está classe de formal Modal, ou seja, até que a instância termine sua execução e o controle retornará para o método main. Para finalizar, terminamos o loop. Foi utilizada uma estratégia de loop infinito e com saida através de break, aqui poderíamos testar também a opção e sair. Após, uma mensagem de finalização e encerramos a execução da aplicação. Do mesmo modo que esta classe foi criada, cria-se uma nova classe chamada Leitor. Esta classe tem a finalidade de permitir que o usuário utilize o console para fazer a entrada de dados. Ela é utilizada em toda a nossa pequena aplicação. O código fonte para a classe Leitor é: 69 import import import import java.io.BufferedReader; java.io.IOException; java.io.InputStream; java.io.InputStreamReader; public class Leitor { private BufferedReader buffer; public Leitor(InputStream inputStream) { buffer = new BufferedReader(new InputStreamReader(inputStream)); } /** * @param Descrição * @return Entrada do teclado */ public String leDoTeclado(String descricao) { String texto = null; try { System.out.print(descricao + "> "); texto = buffer.readLine(); } catch (IOException e) { } return texto; } } Listagem 22: Leitor.java – Classe utilitária para entrada de dados pelo console 6.2 – Descrição da classe Leitor Na listagem 23 podemos visualizar a classe LeitorHibernateUtility. Nessa classe foi criada para permitir a entrada de dados pelo console. Ela contém apenas um método, o leDoTeclado(), que realiza esta operação. Seu funcionamento é bem simples, quando a classe é criada, no seu construtor é passado um InputStream, no nosso caso utilizamos o objeto System.in, que será a fonte dos dados de entrada. Para sua utilização, envia-se uma mensagem através do método leDoTeclado, passando uma mensagem. Este mostra a mensagem na saida padrão, fica esperado uma entrada do console no estado Modal, quando o usuário tecla Enter, ele retorna uma String contendo o que o usuário digitou. Cria-se uma nova classe chamada ListarCursos. O código fonte para ela é: 70 import java.util.Iterator; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.HibernateException; public class ListaCursos { /** * @param mostraDisciplinas */ public ListaCursos(boolean mostraDisciplinas) { try { Session session = HibernateUtility.getSession(); Query query = session.createQuery("from Curso a"); System.out.println("========================================"); System.out.println("============= Resultados ============="); System.out.println("========================================"); for (Iterator it = query.iterate(); it.hasNext();) { // exibe os cursos Curso curso = (Curso) it.next(); System.out.println("Curso: " + curso.getId() + "-" + curso.getNome() + " - " + curso.getDescricao()); // exibe as disciplinas dos cursos if (mostraDisciplinas) for (Iterator it2 = curso.getDisciplinas().iterator(); it2 .hasNext();) { Disciplina disciplina = (Disciplina) it2.next(); System.out.println("Disciplina:" + disciplina.getId() + "-" + disciplina.getNome() + " - " + disciplina.getEmenta()); } } session.close(); } catch (HibernateException e) { e.printStackTrace(); } 6.3 – Descrevendo o código da classe ListarCurso } } Listagem 23: ListarCursos.java – Listagem de Cursos Esta classe tem o objetivo de listar os cursos e suas disciplinas associadas. Ela irá criar as instâncias e mostrará utilizando a saída padrão. 71 Observando o código-fonte da listagem 25 vemos o procedimento para configuração do Hibernate, através da classe HibernateUtility, é realizado na linha 13. Ele cria uma intancia da classe Session do próprio Hibernate. A verificação dos arquivos XML é feita na linha 13. Se nenhuma exceção for gerada no parser do arquivo XML, obtemos uma instância de Session para a aplicação. A partir desse momento, estamos prontos para realizar a consulta. Para a criação da consulta utilizamos um objeto da classe Query. Ela é que fisicamente pega os dados do banco de dados e cria os objetos, retornando-os de forma a poderem ser utilizados com um Iterator. Fato que acontece na linha 18. A partir deste ponto, pode-se trabalhar da mesma maneira em que faz para utilizar um Set ou um List. A maior diferença é que no final, deve-se fechar o objeto Session, através do método close(). E de resto só uma verificação de exceção para a classe HibernateException. Agora, cria-se uma nova classe chamada NovoCurso. import org.hibernate.Session; import org.hibernate.HibernateException; import org.hibernate.Transaction; public class NovoCurso { public NovoCurso() { Listagem 24: NovoCursos.java – Inclusão de Novos Cursos 72 Leitor leitor = new Leitor(System.in); try { Transaction tx = null; Session session = HibernateUtility.getSession(); String sid; // determina o inicio da transacao tx = session.beginTransaction(); Curso curso = new Curso(); // le os dados sid = leitor.leDoTeclado("Digite o número do curso:"); try { Integer iid = new Integer(Integer.parseInt(sid)); curso.setId(iid); } catch (Exception e) { System.out.println("Número inválido!"); } curso.setNome(leitor.leDoTeclado("Digite o nome do curso:")); curso.setDescricao(leitor .leDoTeclado("Digite o descrição do curso:")); // grava os dados session.save(curso); // confirma as alteracoes da transacao no banco (commit) tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } } Listagem 24: NovoCursos.java – Inclusão de Novos Cursos (continuação) 6.4 – Execução da aplicação Antes de executarmos a aplicação, devemos configurar o arquivo hibernate.properties, que armazena as informações de conexão com o banco de dados. Analisamos as linhas 12 a 17 da listagem 10, vemos que as configurações com o MySQL, encontram-se ativas. O arquivo hibernate.cfg.xml deve estar no diretório-raiz da aplicação, para que o Hibernate funcione de maneira adequada. Finalmente estamos prontos. Dentro do Eclipse digite F5 73 Execute a aplicação. Faça o seguinte: Crie um novo Curso chamado “Tecnólogo em Informática”. Escolha a opção de listar cursos. Agora , escolha a opção de listar curso e disciplinas. Perceba que o resultado é o mesmo, pois não existem disciplinas a serem listadas. Agora, crie uma classe chamada NovaDisciplina. import java.util.HashMap; import java.util.Iterator; import java.util.Map; import import import import org.hibernate.Query; org.hibernate.Session; org.hibernate.HibernateException; org.hibernate.Transaction; public class NovaDisciplina { public NovaDisciplina() { Leitor leitor = new Leitor(System.in); try { Transaction tx = null; Session session = HibernateUtility.getSession(); String sid; // determina o inicio da transacao tx = session.beginTransaction(); Disciplina disciplina = new Disciplina(); Map lista = new HashMap(); // le os dados sid = leitor.leDoTeclado("Digite o número da disciplina:"); try { Integer iid = new Integer(Integer.parseInt(sid)); disciplina.setId(iid); } catch (Exception e) { System.out.println("Número inválido!"); } disciplina.setNome(leitor.leDoTeclado("Digite o nome:")); disciplina.setEmenta(leitor .leDoTeclado("Digite o descrição do curso:")); Listagem 25: NovaDisciplina.java – Inclusão de Novas Disciplinas 74 // lista artigos existentes Query query = session.createQuery("from Curso"); for (Iterator it = query.iterate(); it.hasNext();) { Curso curso = (Curso) it.next(); System.out.println("> " + curso.getId() + "-" + curso.getNome() + " - " + curso.getDescricao()); lista.put(curso.getId().toString(), curso); } int codigoCurso = Integer.parseInt(leitor .leDoTeclado("Digite o numero do artigo:")); disciplina.setCurso((Curso) lista.get(String.valueOf(codigoCurso))); // grava os dados session.save(disciplina); // confirma as alteracoes da transacao no banco (commit) tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } Listagem 25: NovaDisciplina.java – Inclusão de Novas Disciplinas (continuação) Feito isto, altere a classe AplicacaoUniversidade, acrescentando o seguinte código em destaque: case 3: new ListaCursos(true); break; case 4: new NovoCurso(); break; case 5: new NovaDisciplina(); break; default: throw new NumberFormatException(); Listagem 26: AplicacaoUniversidade.java – Detalhe para chamada de Lista de Cursos e Novas Disciplinas Listagem 29: AplicacaoUniversidade.java – Detalhe para chamada de Inclusão de Novas Disciplinas 6.5 – Recuperação das informações Novamente, execute a aplicação e faça o seguinte: 75 Escolha a opção de listar curso e disciplinas e observe os resultados. Crie uma nova Disciplina chamada “Introdução à Ciência da Computação” e associe-a ao curso “Tecnólogo em Informática”. Agora , escolha a opção de listar curso e disciplinas novamente. Perceba que o resultado não é mais o mesmo, pois já existem disciplinas a serem listadas com os cursos. Quando se associa uma disciplina ao curso, e ela é salva, quando o curso é recuperado novamente, a relação com a displina é novamente reestabelecida, isto acontece porque o Hibernate conhece o modelo e cria na memória todos os objetos filhos necessários para para cada objeto. Isto significa que quando um objeto é criado, o modelo está sempre consistente. Crie uma nova classe chamada RemoverDisciplinas. O código fonte para ela é: import java.util.Iterator; import import import import org.hibernate.HibernateException; org.hibernate.Query; org.hibernate.Session; org.hibernate.Transaction; public class RemoverDisciplina { public RemoverDisciplina() { Leitor leitor = new Leitor(System.in); try { Transaction tx = null; Session session = HibernateUtility.getSession(); // determina o inicio da transacao tx = session.beginTransaction(); Disciplina[] lista = new Disciplina[20]; // lista os links existentes Query query = session.createQuery("from Disciplina"); for (Iterator it = query.iterate(); it.hasNext();) { Disciplina disciplina = (Disciplina) it.next(); System.out.println("> " + disciplina.getId() + "-" + disciplina.getEmenta()); lista[Integer.parseInt(disciplina.getId().toString())] = disciplina; } int codigoDisciplina = Integer.parseInt(leitor .leDoTeclado("Digite o numero da disciplina para remover:")); Listagem 27: RemoverDisciplinas.java – Remoção de Disciplinas 76 // remove os dados session.delete(lista[codigoArtigo]); // confirma as alteracoes da transacao no banco (commit) tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } } Listagem 27: RemoverDisciplinas.java – Remoção de Disciplinas (continuação) Feito isto, altere a classe AplicacaoUniversidade, acrescentando o seguinte código em destaque: case 4: new NovoCurso(); break; case 5: new NovaDisciplina(); break; case 6: new RemoveDisciplina(); break; default: throw new NumberFormatException(); Listagem 28: AplicacaoUniversidade.java – Detalhe para chamada de Remoção de Disciplinas Novamente, execute a aplicação e faça o seguinte: Escolha a opção de listar curso e disciplinas e observe os resultados. Remova a Disciplina chamada “Introdução à Ciência da Computação. Agora , escolha a opção de listar curso e disciplinas novamente. Crie uma outra classe chamada AlteraNomeCurso. O código fonte para ela é: 77 import java.util.Iterator; import import import import org.hibernate.Query; org.hibernate.Session; org.hibernate.HibernateException; org.hibernate.Transaction; public class AlteraNomeCurso { public AlteraNomeCurso() { Leitor leitor = new Leitor(System.in); try { Transaction tx = null; Session session = HibernateUtility.getSession(); // determina o inicio da transacao tx = session.beginTransaction(); Curso[] lista = new Curso[20]; // lista artigos existentes Query query = session.createQuery("from Curso"); for (Iterator it = query.iterate(); it.hasNext();) { Curso curso = (Curso) it.next(); System.out.println("> " + curso.getId() + "-" + curso.getNome() + " - por:" + curso.getDescricao()); lista[Integer.parseInt(curso.getId().toString())] = curso; } int codigoCurso = Integer.parseInt(leitor .leDoTeclado("Digite o numero do curso:")); // altera os dados Curso curso = lista[codigoCurso]; curso.setNome(leitor.leDoTeclado("novo nome")); // grava os dados session.update(curso); Listagem 29: AlteraNomeCurso.java – Alteração de Nome do Curso // confirma as alteracoes da transacao no banco (commit) tx.commit(); session.close(); } catch (HibernateException e) { e.printStackTrace(); } } } Listagem 29: AlteraNomeCurso.java – Alteração de Nome do Curso (continuação) 78 Feito isto, altere a classe AplicacaoUniversidade, acrescentando o seguinte código em destaque: case 6: new RemoveDisciplina(); break; case 7: new AlteraNomeCurso(); break; default: throw new NumberFormatException(); Listagem 30: AplicacaoUniversidade.java – Detalhe para chamada de Alteração de Nome do Curso Execute a aplicação e faça o seguinte: Escolha a opção de listar curso. Altere o Eome do Curso de “Tecnólogo em Informática” para “Bacharel em Ciência da Computação”. Escolha a opção de listar curso. Para exercitar-se faça o seguinte: Crie classes para criar, listar, remover e alterar alunos, professores e turmas; Em seguinda, crie uma rotina de matrícula de aluno na turma 6.6 – Conclusão Utilizamos uma aplicação de demonstração que foi modificada para estar mais próxima de um processo real de desenvolvimento. Criamos um aplicativo Java, utilizando J2SDK 1.5, que realiza chamadas às classes e interfaces fornecidas pelo Hibernate. Por motivo de simplificação, não foi demonstrado todo o potencial do Hibernate com o gerenciamento de transações ou a possibilidade de um EJB obter uma fábrica de sessões (SessionFactory), utilizando Java Naming and Directory Interface (JNDI) Lookup. CAPÍTULO 7 – CONCLUSÃO O Hibernate é um framework muito bom, e que facilita muito o desenvolvimento de aplicações que acessam bancos de dados, fazendo com que o programador se preocupe mais com o seu modelo de objeto e seus comportamentos, do que com as tabelas do banco de dados. O Hibernate também evita o trabalho de escrever dúzias de código repetido para fazer as mesmas coisas, como “inserts”, “selects”, “updates” e “deletes” no banco de dados, além de ter duas opções para se montar buscas complexas em grafos de objetos e ainda uma saída para o caso de nada funcionar, usar SQL. Demonstramos através de um exemplo, como realizar o mapeamento OO x SGBDR através dele, um projeto OpenSource e Free. Considerando ainda que estamos utilizando classes Java, as chamadas ao Hibernate poderiam ser realizadas através de Java Server Pages (JSP), Servets ou Enterprise Java Beans (EJB). Mas é importante salientar que o framework não é uma boa opção para todos os tipos de aplicações. Sistemas que fazem uso extensivo de stored procedures, triggers ou que implementam a maior parte da lógica da aplicação no banco de dados, contando com um modelo de objetos “pobre” não irão se beneficiar com o uso do Hibernate. Ele é mais indicado para sistemas que contam com um modelo rico, onde a maior parte da lógica de domínios fica na própria aplicação Java, dependendo pouco de funções específicas do banco de dados. Sua principal vantagem sobre os seus concorrentes é a possibilidade de ser aplicado a bancos de dados 80 existentes, o que permite uma maior continuidade às aplicações legadas, além de se integrar, via banco, a aplicações desenvolvidas em outras tecnologias. Tudo isto, mantendo-se em conformidade com os padrões de desenvolvimento mais atualizados. 8.1 – Considerações Finais O Hibernate tem avançado bastante, e tem conquistado muito mercado. Neste trabalho, foi mostrada apenas uma pequena parte desta solução, uma sugestão para novos estudos seria o acompanhamento da alteração em uma aplicação que usa EJB CMP para utilizar o Hibernate. Eles são frameworks muito diferentes entre si, mas foram criados para uma mesma função. Como o EJB é bastante popular e o Hibernate vem crescendo muito no mercado, a troca de uma solução por outra é algo que pode e deve ser explorado para a melhoria dos projetos de desenvolvimento orientados a objeto em Java. Para este trabalho, não foi encontrada muita informação publicada sobre este assunto, a grande maioria dos dados levantados foi conseguida por artigos em revistas e publicações pela Internet. Existe a necessidade livros e publicações que permitam fazer uma análise mais profunda este assunto, com uma base teórica bem fundamentada e que possa comparar cada alternativa disponível, em seus prós e contras. Um outro estudo, vem da necessidade de desenvolvimento de novas soluções integradas a sistemas legados. A partir de um sistema que esteja em produção, e que fora desenvolvido para utilizar as características de um banco de dados relacional, como fazer para desenvolver uma solução orientada a objetos que funcione em paralelo com este outro sistema, e ao mesmo tempo, tenha toda a desenvolvimento e a modelagem seguindo uma linha orientada a objetos, com o mais avançado em termos de engenharia de software. Este é um campo muito vasto para estudos, onde quanto mais se pesquisa mais surgem novas possibilidades. Vivemos mundo, onde a maioria das aplicações já foi desenvolvida, o grande desafio é como evoluir sem perder, ou esquecer o passado. 81 REFERÊNCIAS BIBLIOGRÁFICAS: 1. LARMAN, C. – Utilizando UML e Padrões – Bookman, 2003; 2. MARTINS,V. – Persistência de Objetos – Bate Byte; Edição 90, Setembro/ 1999; http://www.pr.gov.br/batebyte/edicoes/1999/bb90/objetos.htm acessado em 23/05/05; 3. ALVIM, P & OLIVEIRA, H – Hibernate: Mapeamento OO x SGBDR – SQL Magazine; Edição 2, Ano 1, 2003; 4. LOZANO, F - Persistência Objeto-Relacional com Java, http://www.lozano.eti.br/palestras/persistencia-oo.pdf acessando em 23/05/2005; 5. ALUR, D.; CRUPI, J. & MALKS, D. – Core J2EE Patterns: As melhores estratégia e práticas de design – Rio de Janeiro, Elsevier, 2004; 6. XEXEO, G. & LOPES, J. A. – Métodos de persistência em Java – SQL Magazine; Edição 6, Ano 1, 2003; 7. OLIVEIRA, D - Livre-se do SQL: uma introdução ao Hibernate, http://www.guj.com.br/java.tutorial.artigo.125.1.guj acessando em 02/08/2005; 8. BEZERRA, E. – Mapeando um modelo de classes para um banco de dados relacional – SQL Magazine; Edição 5, Ano 1, 2003; 83 9. BOAGLIO, F. – Hibernate: O framework que veio para simplificar – SQL Magazine; Edição 17, Ano 2, 2005; 10. BOAGLIO, F. – Hibernate: Facilitando o desenvolvimento Web – SQL Magazine; Edição 19, Ano 2, 2005; 11. LINHARES, M - Introdução ao Hibernate 3, http://www.guj.com.br/java.tutorial.artigo.174.1.guj acessando em 02/08/2005; 12. Hibernate Reference Documentation, http://www.hibernate.org/hib_docs/v3/reference/en/pdf/hibernate_reference. pdf acessando em 02/08/2005; 13. ELLIOTT, J - Working with Hibernate in Eclipse, http://www.onjava.com/pub/a/onjava/2004/06/23/hibernate.html?page=1 acessando em 02/08/2005; 14. BAUER, C & KING, G - Get started with Hibernate: Introducing and configuring Hibernate, http://www.javaworld.com/javaworld/jw-10-2004/jw1018-hibernate.html acessando em 02/08/2005; 15. About Middlegen, http://boss.bekk.no/boss/middlegen/ acessado em 02/09/2005; 16. About HiberClipse, http://hiberclipse.sourceforge.net/ acessado em 02/09/2005; 17. Hibernator, http://hibernator.sourceforge.net/ acessado em 02/09/2005; 18. Overview Hibernate Synchronizer, http://hibernatesynch.sourceforge.net/ acessando em 02/09/2005; 19. Hightower, R. – Object-relation mapping without the container, http://www-128.ibm.com/developerworks/java/library/j-hibern/ acessando em 05/09/2005; 20. Cheng, W. & Poddar, P. – Choose the Right Object Persistence, http://www.fawcette.com/javapro/2003_07/online/wcheng_07_10_03/ acessando em 02/12/2005; 21. Cheng, W. & Poddar, P. – JDBC, CMP, or JDO?, http://www.fawcette.com/javapro/2003_07/online/wcheng_07_25_03/ acessando em 02/12/2005; 84 22. Rocha, H. – Entrada e Saída, http://www.argonavis.com.br/cursos/java/j100/java_15.pdf acessando em 05/12/2005; ANEXO ANEXO I: HIBERNATE: INSTALAÇÃO Para se compilar e executar os exemplo descritos neste trabalho serão necessários os seguintes softwares: Ambiente J2SDK; Hibernate; Eclipse; MySQL; MySQL Administrator; MySQL Query Browser; Driver nativo JDBC para o MySQL (Conector/J); Driver JDBC para o Oracle. A.1 – Ambiente J2SDK Para utilização do Java para o desenvolvimento é necessário o J2SDK. O ambiente J2SDK utilizado foi à versão 1.5.0, mas pode ser utilizada qualquer release desde a 1.4.2. Ele pode ser encontrado no endereço http://java.sun.com/j2se/1.5.0/download.html. Siga a instruções até baixá-lo. 87 Uma vez baixado, basta abrir o executável e basta seguir as instruções que ele mesmo faz todas as configurações necessárias. A.2 – Hibernate Agora se tem que realmente instalar o Hibernate. Neste trabalho se utilizará a versão 3.0.5, mas pode ser utilizada uma versão superior. Ele pode se encontrado no endereço http://www.hibernate.org/download. Siga a instruções até baixá-lo. Uma vez baixado, basta abrir o arquivo zipado e então extrai-lo em C:\. Ele irá gerar uma nova pasta chamada C:\HIBERNATE_3.0. A.3 – Eclipse Precisa-se também de um ambiente de desenvolvimento para facilitar a edição do código fonte. O ambiente de desenvolvimento utilizado foi o Eclipse versão 3.1.0. Ele pode se encontrado no endereço http://www.eclipse.org/download. Siga a instruções até baixá-lo. Uma vez baixado, basta abrir o arquivo zipado e então extrai-lo em C:\. Ele irá gerar uma nova pasta chamada C:\ECLIPSE. O Eclipse possui a vantagem de possuir várias extensões que facilitam o desenvolvimento utilizando a ferramenta Hibernate, em especial o HiberClipse que permite ao desenvolvedor gerar automaticamente os arquivos de mapeamento. Além desta, as outras extensões podem ser também baixadas da Internet, mas falaremos nelas a seguir. A.4 – Servidor MySQL Para o armazenamento físico dos dados precisa-se de uma banco de dados relacional cliente/servidor, ou seja, que tenha suporte a linguagem SQL. O 88 banco de dados utilizado foi o MySQL versão 4.1.13. Ele pode ser baixado no endereço http://www.mysql.org/download, siga a instruções até baixá-lo. Uma vez baixado, basta abrir o executável e basta seguir as instruções que ele mesmo faz todas as configurações necessárias. A.5 – MySQL Administrator Como se estará utilizando um banco de dados relacional, e neste caso, se irá utilizar o MySQL, também se precisa de uma ferramenta para administração que permita iniciar, configurar e gerenciar o banco de dados. Para este banco, ele não vem com nenhuma ferramenta gráfica no pacote padrão. As ferramentas são baixadas e instaladas separadamente. A ferramenta de administração utilizada foi o MySQL Administrator versão 1.1.0. Ele pode ser baixado no endereço http://www.mysql.org/download, siga a instruções até baixá-lo. Uma vez baixado, basta abrir o executável e basta seguir as instruções que ele mesmo faz todas as configurações necessárias. A.6 – MySQL Query Browser Para facilitar a consulta dos dados armazenados no banco, se utilizará uma ferramenta para consultas SQL. Assim pode-se conferir o que realmente esta indo para o banco e como os dados ficaram armazenados. A ferramenta de consulta SQL utilizada foi o MySQL Query Browser versão 1.1.13. Ele pode ser baixado no endereço http://www.mysql.org/download, siga a instruções até baixá-lo. Uma vez baixado, basta abrir o executável e basta seguir as instruções que ele mesmo faz todas as configurações necessárias. A.7 – Driver nativo JDBC para o MySQL (Conector/J); 89 Para acessar o MySQL pelo Java, vamos precisar do driver JDBC dele. Ele pode ser baixado no endereço http://www.mysql.org/download, siga a instruções até baixá-lo. Uma vez baixado, basta abrir o arquivo zipado e extrailo em algum lugar. Depois de feito isto, crie uma pasta chamada C:\JDBC e copie o arquivo .jar para ela. A.8 – Driver JDBC para o Oracle; Para acessar o Oracle pelo Java, precisa-se do driver JDBC dele. Ele pode ser baixado no endereço http://www.oracle.com/download, siga a instruções até baixá-lo. Uma vez baixado, basta abrir o arquivo zipado e extrailo em algum lugar. Depois de feito isto, crie uma pasta chamada C:\JDBC e copie o arquivo .jar para ela. 90