Hibernate.orgCommunity Documentation

Capítulo 1. Tutorial

1.1. Parte 1 – A primeira aplicação Hibernate
1.1.1. Configuração
1.1.2. A primeira Classe
1.1.3. O mapeamento do arquivo
1.1.4. Configuração do Hibernate
1.1.5. Construindo com o Maven
1.1.6. Inicialização e Auxiliares
1.1.7. Carregando e salvando objetos
1.2. Parte 2 - Mapeando associações
1.2.1. Mapeando a classe Person
1.2.2. Uma associação unidirecional baseada em Configuração
1.2.3. Trabalhando a associação
1.2.4. Coleção de valores
1.2.5. Associações bidirecionais
1.2.6. Trabalhando com links bidirecionais
1.3. EventManager um aplicativo da web
1.3.1. Criando um servlet básico
1.3.2. Processando e renderizando
1.3.3. Implementando e testando
1.4. Sumário

Intencionado para novos usuários, este capítulo fornece uma introdução detalhada do Hibernate, começando com um aplicativo simples usando um banco de dados em memória. O tutorial é baseado num tutorial anterior desenvolvido por Michael Gloegl. Todo o código está contido no diretório tutorials/web da fonte do projeto.

Importante

Este tutorial espera que o usuário tenha conhecimento de ambos Java e SQL. Caso você tenha um conhecimento limitado do JAVA ou SQL, é recomendado que você inicie com uma boa introdução àquela tecnologia, antes de tentar entender o Hibernate.

Nota

Esta distribuição contém outro aplicativo de amostra sob o diretório de fonte do projeto tutorial/eg.

Vamos supor que precisemos de uma aplicação com um banco de dados pequeno que possa armazenar e atender os eventos que queremos, além das informações sobre os hosts destes eventos.

Nota

Mesmo que usando qualquer banco de dados do qual você se sinta confortável, nós usaremos HSQLDB (o em memória, banco de dados Java) para evitar a descrição de instalação/configuração de quaisquer servidores do banco de dados.

O primeiro passo em que precisamos tomar é configurar o ambiente de desenvolvimento. Nós usaremos o "layout padrão" suportado por muitas ferramentas de construção, tais como Maven. Maven, em particular, possui um excelente recurso de descrição disto layout. Assim como este tutorial deve ser um aplicativo da web, nós criaremos e faremos uso dos diretórios src/main/java, src/main/resources e src/main/webapp.

Nós usaremos Maven neste tutorial, tirando vantagem destas capacidades de dependência transitiva assim como a habilidade de muitos IDEs de configurar automaticamente um projeto baseado no descritor maven.


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>org.hibernate.tutorials</groupId>
    <artifactId>hibernate-tutorial</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>First Hibernate Tutorial</name>

    <build>
         <!-- we dont want the version to be part of the generated war file name -->
         <finalName>${artifactId}</finalName>
    </build>

    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
        </dependency>

        <!-- Because this is a web app, we also have a dependency on the servlet api. -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
        </dependency>

        <!-- Hibernate uses slf4j for logging, for our purposes here use the simple backend -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
        </dependency>

        <!-- Hibernate gives you a choice of bytecode providers between cglib and javassist -->
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
        </dependency>
    </dependencies>

</project>

Dica

It is not a requirement to use Maven. If you wish to use something else to build this tutorial (such as Ant), the layout will remain the same. The only change is that you will need to manually account for all the needed dependencies. If you use something like Ivy providing transitive dependency management you would still use the dependencies mentioned below. Otherwise, you'd need to grab all dependencies, both explicit and transitive, and add them to the project's classpath. If working from the Hibernate distribution bundle, this would mean hibernate3.jar, all artifacts in the lib/required directory and all files from either the lib/bytecode/cglib or lib/bytecode/javassist directory; additionally you will need both the servlet-api jar and one of the slf4j logging backends.

Salve este arquivo como pom.xml no diretório raiz do projeto.

Agora, iremos criar uma classe que representa o evento que queremos armazenar na base de dados. Isto é uma classe JavaBean simples com algumas propriedades:

package org.hibernate.tutorial.domain;


import java.util.Date;
public class Event {
    private Long id;
    private String title;
    private Date date;
    public Event() {}
    public Long getId() {
        return id;
    }
    private void setId(Long id) {
        this.id = id;
    }
    public Date getDate() {
        return date;
    }
    public void setDate(Date date) {
        this.date = date;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}

Você pode ver que esta classe usa o padrão JavaBean para o nome convencional dos métodos de propriedade getter e setter, como também a visibilidade privada dos campos. Este é um padrão de projeto recomendado, mas não requerido. O Hibernate pode também acessar campos diretamente, o benefício para os métodos de acesso é a robustez para o refactoring.

A propriedade id mantém um único valor de identificação para um evento particular. Todas as classes persistentes da entidade (bem como aquelas classes dependentes de menos importância) precisam de uma propriedade de identificação, caso nós queiramos usar o conjunto completo de características do Hibernate. De fato, a maioria das aplicações, especialmente. aplicações web, precisam distinguir os objetos pelo identificador. Portanto, você deverá considerar esta, uma característica ao invés de uma limitação. Porém, nós normalmente não manipulamos a identidade de um objeto, conseqüentemente o método setter deverá ser privado. O Hibernate somente nomeará os identificadores quando um objeto for salvo. O Hibernate pode acessar métodos públicos, privados, e protegidos, como também campos públicos, privados, protegidos diretamente. A escolha é sua e você pode adaptar seu projeto de aplicação.

O construtor sem argumentos é um requerimento para todas as classes persistentes; O Hibernate precisa criar para você os objetos usando Java Reflection. O construtor pode ser privado, porém, a visibilidade do pacote é requerida para a procuração da geração em tempo de execução e recuperação eficiente dos dados sem a instrumentação de bytecode.

Salve este arquivo no diretório src/main/java/org/hibernate/tutorial/domain.

O Hibernate precisa saber como carregar e armazenar objetos da classe de persistência. É aqui que o mapeamento do arquivo do Hibernate entrará em jogo. O arquivo mapeado informa ao Hibernate, qual tabela no banco de dados ele deverá acessar, e quais as colunas na tabela ele deverá usar.

A estrutura básica de um arquivo de mapeamento é parecida com:


<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="org.hibernate.tutorial.domain">
[...]
</hibernate-mapping
>

Note que o Hibernate DTD é muito sofisticado. Você pode usar isso para auto-conclusão no mapeamento XML dos elementos e funções no seu editor ou IDE. Você também pode abrir o arquivo DTD no seu editor. Esta é a maneira mais fácil de ter uma visão geral de todos os elementos e funções e dos padrões, como também alguns comentários. Note que o Hibernate não irá carregar o arquivo DTD da web, e sim da classpath da aplicação. O arquivo DTD está incluído no hibernate-core.jar (como também no hibernate3.jar, caso usando a vinculação de distribuição.

Entre as duas tags hibernate-mapping, inclua um elemento class. Todas as classes persistentes da entidade (novamente, poderá haver mais tarde, dependências sobre as classes que não são classes-primárias de entidades) necessitam do tal mapeamento, para uma tabela na base de dados SQL:


<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">

    </class>

</hibernate-mapping>

Até agora, informamos o Hibernate sobre como fazer para persistir e carregar objetos da classe Event da tabela EVENTS, cada instância representada por uma coluna na tabela. Agora, continuaremos com o mapeamento de uma única propriedade identificadora para as chaves primárias da tabela. Além disso, como não precisamos nos preocupar em manipular este identificador, iremos configurar uma estratégia de geração de id’s do Hibernate para uma coluna de chave primária substituta:


<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">
        <id name="id" column="EVENT_ID">
            <generator class="native"/>
        </id>
    </class>

</hibernate-mapping>

O elemento id é a declaração de uma propriedade do identificador. O atributo do mapeamento name="id" declara que o nome da propriedade JavaBeans e informa o Hibernate a utilizar os métodos getId() and setId() para acessar a propriedade. A atributo da coluna informa o Hibernate qual coluna da tabela EVENTS mantém o valor de chave primária.

O elemento generator aninhado especifica a estratégia da geração do identificador (como os valores do identificador são gerados?). Neste caso, nós escolhemos native, do qual oferece um nível de portabilidade dependendo no dialeto do banco de dados configurado. O Hibernate suporta o banco de dados gerado, globalmente único, assim como a aplicação determinada, identificadores. A geração do valor do identificador é também um dos muitos pontos de extensão do Hibernate e você pode realizar o plugin na sua própria estratégia.

Finalmente, incluiremos as declarações para as propriedades persistentes da classe no arquivo mapeado. Por padrão, nenhuma das propriedades da classe é considerada persistente:



<hibernate-mapping package="org.hibernate.tutorial.domain">

    <class name="Event" table="EVENTS">
        <id name="id" column="EVENT_ID">
            <generator class="native"/>
        </id>
        <property name="date" type="timestamp" column="EVENT_DATE"/>
        <property name="title"/>
    </class>

</hibernate-mapping>

Assim como com o elemento id, a função name do elemento property informa ao Hibernate qual método getter e setter deverá usar. Assim, neste caso, o Hibernate irá procurar pelos métodos getDate(), setDate(), getTitle() e setTitle().

Nota

Porque fazer o mapeamento da propriedade date incluído na função column, e no title não fazer? Sem a função column o Hibernate, por padrão, utiliza o nome da propriedade como o nome da coluna. Isto funciona bem para o title. Entretanto, o date é uma palavra-chave reservada na maioria dos bancos de dados, por isso seria melhor mapeá-lo com um nome diferente.

O mapeamento do title também não possui a função type. O tipo que declaramos e utilizamos nos arquivos mapeados, não são como você esperava, ou seja, funções de dados Java. Eles também não são como os tipos de base de dados SQL. Esses tipos podem ser chamados de Tipos de mapeamento Hibernate, que são conversores que podem traduzir tipos de dados do Java para os tipos de dados SQL e vice-versa. Novamente, o Hibernate irá tentar determinar a conversão correta e mapeará o type próprio, caso o tipo da função não estiver presente no mapeamento. Em alguns casos, esta detecção automática (que usa Reflection sobre as classes Java) poderá não ter o padrão que você espera ou necessita. Este é o caso com a propriedade date. O Hibernate não sabe se a propriedade, que é do java.util.Date, pode mapear para uma coluna do tipo date do SQL, timestamp ou time. Nós preservamos as informações sobre datas e horas pelo mapeamento da propriedade com um conversor timestamp.

Dica

O Hibernate realiza esta determinação de tipo de mapeamento usando a reflexão quando os arquivos de mapeamentos são processados. Isto pode levar tempo e recursos, portanto se você inicializar o desempenho, será importante que você considere claramente a definição do tipo para uso.

Salve este arquivo de mapeamento como src/main/resources/org/hibernate/tutorial/domain/Event.hbm.xml.

Nestas alturas, você deve possuir a classe persistente e seu arquivo de mapeamento prontos. É o momento de configurar o Hibernate. Primeiro, vamos configurar o HSQLDB para rodar no "modo do servidor".

Nós utilizaremos o Maven exec plugin para lançar o servidor HSQLDB pela execução: mvn exec:java -Dexec.mainClass="org.hsqldb.Server" -Dexec.args="-database.0 file:target/data/tutorial". Você pode ver ele iniciando e vinculando ao soquete TCP/IP, aqui será onde nossa aplicação irá se conectar depois. Se você deseja iniciar uma nova base de dados durante este tutorial, finalize o HSQLDB, delete todos os arquivos no diretório target/data, e inicie o HSQLBD novamente.

O Hibernate conectará ao banco de dados no lugar de sua aplicação, portanto ele precisará saber como obter as conexões. Para este tutorial nós usaremos um pool de conexão autônomo (ao invés de javax.sql.DataSource). O Hibernate vem com o suporte para dois terços dos pools de conexão JDBC de código aberto: c3p0 e proxool. No entanto, nós usaremos o pool de conexão interna do Hibernate para este tutorial.

Cuidado

O pool de conexão interna do Hibernate não é recomendado para uso de produção. Ele possui deficiência em diversos recursos encontrados em qualquer pool de conexão apropriado.

Para as configurações do Hibernate, nós podemos usar um arquivo simples hibernate.properties, um arquivo mais sofisticado hibernate.cfg.xml ou até mesmo uma instalação programática completa. A maioria dos usuários prefere utilizar o arquivo de configuração XML:


<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class"
>org.hsqldb.jdbcDriver</property>
        <property name="connection.url"
>jdbc:hsqldb:hsql://localhost</property>
        <property name="connection.username"
>sa</property>
        <property name="connection.password"
></property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size"
>1</property>

        <!-- SQL dialect -->
        <property name="dialect"
>org.hibernate.dialect.HSQLDialect</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class"
>thread</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class"
>org.hibernate.cache.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql"
>true</property>

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto"
>update</property>

        <mapping resource="org/hibernate/tutorial/domain/Event.hbm.xml"/>

    </session-factory>

</hibernate-configuration
>

Nota

Perceba que este arquivo de configuração especifica um DTD diferente

Configure a SessionFactory do Hibernate. A SessionFactory é uma fábrica global responsável por uma base de dados particular. Se você tiver diversas bases de dados, use diversas configurações <session-factory>, geralmente em diversos arquivos de configuração, para uma inicialização mais fácil.

Os primeiros quatro elementos property contêm a configuração necessária para a conexão JBDC. O elemento property do dialeto especifica a variante do SQL particular que o Hibernate gera.

Dica

In most cases, Hibernate is able to properly determine which dialect to use. See Seção 28.3, “Resolução do Dialeto” for more information.

O gerenciamento automático de sessão do Hibernate para contextos de persistência é bastante útil neste contexto. A opção hbm2ddl.auto habilita a geração automática de esquemas da base de dados, diretamente na base de dados. Isto também pode ser naturalmente desligado apenas removendo a opção de configuração ou redirecionado para um arquivo com ajuda do SchemaExport na tarefa do Ant. Finalmente, iremos adicionar os arquivos das classes de persistência mapeadas na configuração.

Salve este arquivo como hibernate.cfg.xml no diretório src/main/resources.

Nós iremos construir agora o tutorial com Maven. Você necessitará que o Maven esteja instalado; ele está disponível a partir do Maven download page. O Maven gravará o arquivo /pom.xml que criamos anteriormente, além de saber como executar algumas tarefas do projeto básico. Primeiro, vamos rodar o objetivo compile para nos certificarmos de que tudo foi compilado até agora:

[hibernateTutorial]$ mvn compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Building First Hibernate Tutorial
[INFO]    task-segment: [compile]
[INFO] ------------------------------------------------------------------------
[INFO] [resources:resources]
[INFO] Using default encoding to copy filtered resources.
[INFO] [compiler:compile]
[INFO] Compiling 1 source file to /home/steve/projects/sandbox/hibernateTutorial/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2 seconds
[INFO] Finished at: Tue Jun 09 12:25:25 CDT 2009
[INFO] Final Memory: 5M/547M
[INFO] ------------------------------------------------------------------------

É hora de carregar e armazenar alguns objetos Event, mas primeiro nós temos de completar a instalação com algum código de infraestrutura. Você precisa inicializar o Hibernate pela construção de um objeto org.hibernate.SessionFactory global e o armazenamento dele em algum lugar de fácil acesso para o código da aplicação. O org.hibernate.SessionFactory é usado para obter instâncias org.hibernate.Session. O org.hibernate.Session representa uma unidade de single-threaded de trabalho. O org.hibernate.SessionFactory é um objeto global thread-safe, instanciado uma vez.

Criaremos uma classe de ajuda HibernateUtil, que cuida da inicialização e faz acesso a uma org.hibernate.SessionFactory mais conveniente.

package org.hibernate.tutorial.util;


import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
    private static final SessionFactory sessionFactory = buildSessionFactory();
    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new Configuration().configure().buildSessionFactory();
        }
        catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }
    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }
}

Salve este código como src/main/java/org/hibernate/tutorial/util/HibernateUtil.java

Esta classe não só produz uma referência org.hibernate.SessionFactory global em seu inicializador estático, mas também esconde o fato de que utiliza um autônomo estático. Nós poderemos buscar pela referência org.hibernate.SessionFactory a partir do JNDI no servidor da aplicação ou qualquer outra localização para este assunto.

Se você der um nome à SessionFactory em seu arquivo de configuração, o Hibernate irá, de fato, tentar vinculá-lo ao JNDI sob aquele nome, depois que estiver construído. Outra opção melhor seria usar a implementação JMX e deixar o recipiente JMX capaz, instanciar e vincular um HibernateService ao JNDI. Essas opções avançadas são discutidas no documento de referência do Hibernate. Tais opções avançadas serão discutidas mais tarde.

Você precisará agora configurar um sistema de logging. O Hibernate usa logging comuns e lhe oferece a escolha entre o Log4j e o logging do JDK 1.4 . A maioria dos desenvolvedores prefere o Log4j: copie log4j.properties da distribuição do Hibernate no diretório etc/, para seu diretório src, depois vá em hibernate.cfg.xml. Dê uma olhada no exemplo de configuração e mude as configurações se você quiser ter uma saída mais detalhada. Por padrão, apenas as mensagens de inicialização do Hibernate são mostradas no stdout.

O tutorial de infra-estrutura está completo e nós já estamos preparados para algum trabalho de verdade com o Hibernate.

We are now ready to start doing some real work with Hibernate. Let's start by writing an EventManager class with a main() method:

package org.hibernate.tutorial;


import org.hibernate.Session;
import java.util.*;
import org.hibernate.tutorial.domain.Event;
import org.hibernate.tutorial.util.HibernateUtil;
public class EventManager {
    public static void main(String[] args) {
        EventManager mgr = new EventManager();
        if (args[0].equals("store")) {
            mgr.createAndStoreEvent("My Event", new Date());
        }
        HibernateUtil.getSessionFactory().close();
    }
    private void createAndStoreEvent(String title, Date theDate) {
        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        Event theEvent = new Event();
        theEvent.setTitle(title);
        theEvent.setDate(theDate);
        session.save(theEvent);
        session.getTransaction().commit();
    }
}

Em createAndStoreEvent(), criamos um novo objeto de Event, e passamos para o Hibernate. O Hibernate sabe como tomar conta do SQL e executa INSERTs no banco de dados.

A org.hibernate.Session is designed to represent a single unit of work (a single atomic piece of work to be performed). For now we will keep things simple and assume a one-to-one granularity between a Hibernate org.hibernate.Session and a database transaction. To shield our code from the actual underlying transaction system we use the Hibernate org.hibernate.Transaction API. In this particular case we are using JDBC-based transactional semantics, but it could also run with JTA.

O que a sessionFactory.getCurrentSession() faz? Primeiro, você pode chamar quantas vezes e de onde quiser, assim que você receber sua org.hibernate.SessionFactory. O método getCurrentSession() sempre retorna à unidade de trabalho "atual". Você se lembra que nós mudamos a opção de configuração desse mecanismo para "thread" em nosso src/main/resources/hibernate.cfg.xml? Devido a esta configuração, o contexto de uma unidade de trabalho atual estará vinculada à thread Java atual que executa nossa aplicação.

Um org.hibernate.Session começa quando for necessária, quando é feita a primeira chamada à getCurrentSession(). É então limitada pelo Hibernate para a thread atual. Quando a transação termina, tanto com commit quanto rollback, o Hibernate também desvincula a Session da thread e fecha isso pra você. Se você chamar getCurrentSession() novamente, você receberá uma nova Session e poderá começar uma nova unidade de trabalho.

Em relação ao escopo da unidade de trabalho, o Hibernate org.hibernate.Session deve ser utilizado para executar uma ou mais operações do banco de dados? O exemplo acima utiliza uma Session para cada operação. Isto é pura coincidência, o exemplo simplesmente não é complexo o bastante para mostrar qualquer outra abordagem. O escopo de um Hibernate org.hibernate.Session é flexível, mas você nunca deve configurar seu aplicativo para utilizar um novo Hibernate org.hibernate.Session para aoperação de banco de dados every. Portanto, mesmo que você o veja algumas vezes mais nos seguintes exemplos, considere session-per-operation como um anti-modelo. Um aplicativo da web real será demonstrado mais adiante neste tutorial.

See Capítulo 13, Transações e Concorrência for more information about transaction handling and demarcation. The previous example also skipped any error handling and rollback.

Para rodar isto, nós faremos uso do Maven exec plugin para chamar nossa classe com a instalação do classpath necessária: mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="store"

Nota

Você precisa executar o mvn compile primeiramente.

Você deverá ver, após a compilação, a inicialização do Hibernate e, dependendo da sua configuração, muito log de saída. No final, você verá a seguinte linha:

[java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?)

Este é o INSERT executado pelo Hibernate.

Adicionamos uma opção para o método principal com o objetivo de listar os eventos arquivados:

        if (args[0].equals("store")) {

            mgr.createAndStoreEvent("My Event", new Date());
        }
        else if (args[0].equals("list")) {
            List events = mgr.listEvents();
            for (int i = 0; i < events.size(); i++) {
                Event theEvent = (Event) events.get(i);
                System.out.println(
                        "Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()
                );
            }
        }

Nos também adicionamos um novo listEvents() method is also added:

    private List listEvents() {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        List result = session.createQuery("from Event").list();
        session.getTransaction().commit();
        return result;
    }

Here, we are using a Hibernate Query Language (HQL) query to load all existing Event objects from the database. Hibernate will generate the appropriate SQL, send it to the database and populate Event objects with the data. You can create more complex queries with HQL. See Capítulo 16, HQL: A Linguagem de Consultas do Hibernate for more information.

Agora podemos chamar nossa nova funcionalidade usando, novamente, o Maven exec plugin: mvn exec:java -Dexec.mainClass="org.hibernate.tutorial.EventManager" -Dexec.args="list"

Nós mapeamos uma classe de entidade de persistência para uma tabela. Agora vamos continuar e adicionar algumas associações de classe. Primeiro iremos adicionar pessoas à nossa aplicação e armazenar os eventos em que elas participam.

Iremos adicionar uma coleção de eventos na classe Person. Dessa forma, poderemos navegar pelos eventos de uma pessoa em particular, sem executar uma consulta explicitamente, apenas chamando Person#getEvents. As associações de valores múltiplos são representadas no Hibernate por um dos contratos do Java Collection Framework; aqui nós escolhemos um java.util.Set, uma vez que a coleção não conterá elementos duplicados e a ordem não é relevante em nossos exemplos:

public class Person {


    private Set events = new HashSet();
    public Set getEvents() {
        return events;
    }
    public void setEvents(Set events) {
        this.events = events;
    }
}

Antes de mapearmos esta associação, pense no outro lado. Claramente, poderíamos apenas fazer isto de forma unidirecional. Ou poderíamos criar outra coleção no Event, se quisermos navegar de ambas direções. Isto não é necessário, de uma perspectiva funcional. Você poderá sempre executar uma consulta explícita para recuperar os participantes de um evento em particular. Esta é uma escolha de design que cabe a você, mas o que é claro nessa discussão é a multiplicidade da associação: "muitos" válidos em ambos os lados, nós chamamos isto de uma associação muitos-para-muitos. Daqui pra frente, usaremos o mapeamento muitos-para-muitos do Hibernate:


<class name="Person" table="PERSON">
    <id name="id" column="PERSON_ID">
        <generator class="native"/>
    </id>
    <property name="age"/>
    <property name="firstname"/>
    <property name="lastname"/>

    <set name="events" table="PERSON_EVENT">
        <key column="PERSON_ID"/>
        <many-to-many column="EVENT_ID" class="Event"/>
    </set>

</class>

O Hibernate suporta todo tipo de mapeamento de coleção, sendo um set mais comum. Para uma associação muitos-para-muitos ou relacionamento de entidade n:m, é necessária uma tabela de associação. Cada linha nessa tabela representa um link entre uma pessoa e um evento. O nome da tabela é configurado com a função table do elemento set. O nome da coluna identificadora na associação, pelo lado da pessoa, é definido com o elemento key, o nome da coluna pelo lado dos eventos, é definido com a função column do many-to-many. Você também precisa dizer para o Hibernate a classe dos objetos na sua coleção (a classe do outro lado das coleções de referência).

O esquema de mapeamento para o banco de dados está a seguir:

    _____________        __________________
   |             |      |                  |       _____________
   |   EVENTS    |      |   PERSON_EVENT   |      |             |
   |_____________|      |__________________|      |    PERSON   |
   |             |      |                  |      |_____________|
   | *EVENT_ID   | <--> | *EVENT_ID        |      |             |
   |  EVENT_DATE |      | *PERSON_ID       | <--> | *PERSON_ID  |
   |  TITLE      |      |__________________|      |  AGE        |
   |_____________|                                |  FIRSTNAME  |
                                                  |  LASTNAME   |
                                                  |_____________|
 

Vamos reunir algumas pessoas e eventos em um novo método na classe EventManager:

    private void addPersonToEvent(Long personId, Long eventId) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        Person aPerson = (Person) session.load(Person.class, personId);
        Event anEvent = (Event) session.load(Event.class, eventId);
        aPerson.getEvents().add(anEvent);
        session.getTransaction().commit();
    }

Após carregar um Person e um Event, simplesmente modifique a coleção usando os métodos normais de uma coleção. Como você pode ver, não há chamada explícita para update() ou save(); o Hibernate detecta automaticamente que a coleção foi modificada e que necessita ser atualizada. Isso é chamado de checagem suja automática, e você também pode usá-la modificando o nome ou a data de qualquer um dos seus objetos. Desde que eles estejam no estado persistent, ou seja, limitado por uma Session do Hibernate em particular, o Hibernate monitora qualquer alteração e executa o SQL em modo de gravação temporária. O processo de sincronização do estado da memória com o banco de dados, geralmente apenas no final de uma unidade de trabalho, normalmente apenas no final da unidade de trabalho, é chamado de flushing. No nosso código, a unidade de trabalho termina com o commit , ou rollback, da transação do banco de dados.

Você pode também querer carregar pessoas e eventos em diferentes unidades de trabalho. Ou você modifica um objeto fora de um org.hibernate.Session, quando não se encontra no estado persistente (se já esteve neste estado anteriormente, chamamos esse estado de detached). Você pode até mesmo modificar uma coleção quando esta se encontrar no estado detached:

    private void addPersonToEvent(Long personId, Long eventId) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        Person aPerson = (Person) session
                .createQuery("select p from Person p left join fetch p.events where p.id = :pid")
                .setParameter("pid", personId)
                .uniqueResult(); // Eager fetch the collection so we can use it detached
        Event anEvent = (Event) session.load(Event.class, eventId);
        session.getTransaction().commit();
        // End of first unit of work
        aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached
        // Begin second unit of work
        Session session2 = HibernateUtil.getSessionFactory().getCurrentSession();
        session2.beginTransaction();
        session2.update(aPerson); // Reattachment of aPerson
        session2.getTransaction().commit();
    }

A chamada update cria um objeto persistente novamente, pode-se dizer que ele liga o objeto a uma nova unidade de trabalho, assim qualquer modificação que você faça neste objeto enquanto estiver no estado desanexado pode ser salvo no banco de dados. Isso inclui qualquer modificação (adição/exclusão) que você faça em uma coleção da entidade deste objeto.

Bem, isso não é de grande utilidade na nossa situação atual, porém, é um importante conceito que você pode criar em seu próprio aplicativo. No momento, complete este exercício adicionando uma ação ao método principal da classe EventManager e chame-o pela linha de comando. Se você precisar dos identificadores de uma pessoa ou evento - o método save() retornará estes identificadores (você poderá modificar alguns dos métodos anteriores para retornar aquele identificador):

        else if (args[0].equals("addpersontoevent")) {

            Long eventId = mgr.createAndStoreEvent("My Event", new Date());
            Long personId = mgr.createAndStorePerson("Foo", "Bar");
            mgr.addPersonToEvent(personId, eventId);
            System.out.println("Added person " + personId + " to event " + eventId);
        }

Este foi um exemplo de uma associação entre duas classes igualmente importantes: duas entidades. Como mencionado anteriormente, há outras classes e tipos dentro de um modelo típico, geralmente "menos importante". Alguns você já viu, como um int ou uma String. Nós chamamos essas classes de tipos de valores, e suas instâncias dependem de uma entidade particular. As instâncias desses tipos não possuem sua própria identidade, nem são compartilhados entre entidades. Duas pessoas não referenciam o mesmo objeto firstname mesmo se elas tiverem o mesmo objeto firstname. Naturalmente, os tipos de valores não são apenas encontrados dentro da JDK, mas você pode também criar suas classes como, por exemplo, Address ou MonetaryAmount. De fato, no aplicativo Hibernate todas as classes JDK são consideradas tipos de valores.

Você também pode criar uma coleção de tipo de valores. Isso é conceitualmente muito diferente de uma coleção de referências para outras entidades, mas em Java parece ser quase a mesma coisa.

Vamos adicionar uma coleção de endereços de e-mail à entidade Person. Isto será representado como um java.util.Set das instâncias java.lang.String:

    private Set emailAddresses = new HashSet();


    public Set getEmailAddresses() {
        return emailAddresses;
    }
    public void setEmailAddresses(Set emailAddresses) {
        this.emailAddresses = emailAddresses;
    }

Segue abaixo o mapeamento deste Set:


        <set name="emailAddresses" table="PERSON_EMAIL_ADDR">
            <key column="PERSON_ID"/>
            <element type="string" column="EMAIL_ADDR"/>
        </set>

A diferença comparada com o mapeamento anterior se encontra na parte element, que informa ao Hibernate que a coleção não contém referências à outra entidade, mas uma coleção de elementos do tipo String. O nome da tag em minúsculo indica que se trata de um tipo/conversor de mapeamento do Hibernate. Mais uma vez, a função table do elemento set determina o nome da tabela para a coleção. O elemento key define o nome da coluna de chave estrangeira na tabela de coleção. A função column dentro do elemento element define o nome da coluna onde os valores da String serão armazenados.

Segue abaixo o esquema atualizado:

  _____________        __________________
 |             |      |                  |       _____________
 |   EVENTS    |      |   PERSON_EVENT   |      |             |       ___________________
 |_____________|      |__________________|      |    PERSON   |      |                   |
 |             |      |                  |      |_____________|      | PERSON_EMAIL_ADDR |
 | *EVENT_ID   | <--> | *EVENT_ID        |      |             |      |___________________|
 |  EVENT_DATE |      | *PERSON_ID       | <--> | *PERSON_ID  | <--> |  *PERSON_ID       |
 |  TITLE      |      |__________________|      |  AGE        |      |  *EMAIL_ADDR      |
 |_____________|                                |  FIRSTNAME  |      |___________________|
                                                |  LASTNAME   |
                                                |_____________|
 

Você pode observar que a chave primária da tabela da coleção é na verdade uma chave composta, usando as duas colunas. Isso também implica que cada pessoa não pode ter endereços de e-mail duplicados, o que é exatamente a semântica que precisamos para um set em Java.

Você pode agora tentar adicionar elementos à essa coleção, do mesmo modo que fizemos anteriormente ligando pessoas e eventos. É o mesmo código em Java:

    private void addEmailToPerson(Long personId, String emailAddress) {

        Session session = HibernateUtil.getSessionFactory().getCurrentSession();
        session.beginTransaction();
        Person aPerson = (Person) session.load(Person.class, personId);
        // adding to the emailAddress collection might trigger a lazy load of the collection
        aPerson.getEmailAddresses().add(emailAddress);
        session.getTransaction().commit();
    }

Desta vez não utilizamos uma consulta fetch (busca) para inicializar a coleção. Monitore o log SQL e tente otimizá-lo com árdua busca.

Agora iremos mapear uma associação bidirecional. Você fará uma associação entre o trabalho person e event de ambos os lados em Java. O esquema do banco de dados acima não muda, de forma que você continua possuir a multiplicidade muitos-para-muitos.

Primeiramente, adicione uma coleção de participantes à classe Event:

    private Set participants = new HashSet();


    public Set getParticipants() {
        return participants;
    }
    public void setParticipants(Set participants) {
        this.participants = participants;
    }

Agora mapeie este lado da associação em Event.hbm.xml.


        <set name="participants" table="PERSON_EVENT" inverse="true">
            <key column="EVENT_ID"/>
            <many-to-many column="PERSON_ID" class="Person"/>
        </set
>

Como você pode ver, esses são mapeamentos set normais em ambos documentos de mapeamento. Observe que os nomes das colunas em key e many-to-many estão trocados em ambos os documentos de mapeamento. A adição mais importante feita está na função inverse="true" no elemento set da coleção da classe Event.

Isso significa que o Hibernate deve pegar o outro lado, a classe Person, quando precisar encontrar informação sobre a relação entre as duas entidades. Isso será muito mais fácil de entender quando você analisar como a relação bidirecional entre as entidades é criada.

Primeiro, tenha em mente que o Hibernate não afeta a semântica normal do Java. Como foi que criamos um link entre uma Person e um Event no exemplo unidirecional? Adicionamos uma instância de Event, da coleção de referências de eventos, à uma instância de Person. Então, obviamente, se quisermos que este link funcione bidirecionalmente, devemos fazer a mesma coisa para o outro lado, adicionando uma referência de Person na coleção de um Event. Essa "configuração de link de ambos os lados" é absolutamente necessária e você nunca deve esquecer de fazê-la.

Muitos desenvolvedores programam de maneira defensiva e criam métodos de gerenciamento de um link que ajustam-se corretamente em ambos os lados (como por exemplo, em Person):

    protected Set getEvents() {

        return events;
    }
    protected void setEvents(Set events) {
        this.events = events;
    }
    public void addToEvent(Event event) {
        this.getEvents().add(event);
        event.getParticipants().add(this);
    }
    public void removeFromEvent(Event event) {
        this.getEvents().remove(event);
        event.getParticipants().remove(this);
    }

Observe que os métodos set e get da coleção estão protegidos. Isso permite que classes e subclasses do mesmo pacote continuem acessando os métodos, mas evita que qualquer outra classe, que não esteja no mesmo pacote, acesse a coleção diretamente. Repita os passos para a coleção do outro lado.

E sobre o mapeamento da função inverse? Para você, e para o Java, um link bidirecional é simplesmente uma questão de configurar corretamente as referências de ambos os lados. O Hibernate, entretanto, não possui informação necessária para ajustar corretamente as instruções INSERT e UPDATE do SQL (para evitar violações de restrição) e precisa de ajuda para manipular as associações bidirecionais de forma apropriada. Ao fazer um lado da associação com a função inverse, você instrui o Hibernate para basicamente ignorá-lo, considerando-o uma cópia do outro lado. Isso é o necessário para o Hibernate compreender todas as possibilidades quando transformar um modelo de navegação bidirecional em esquema de banco de dados do SQL. As regras que você precisa lembrar são diretas: todas as associações bidirecionais necessitam que um lado possua a função inverse. Em uma associação de um-para-muitos, precisará ser o lado de "muitos", já em uma associação de muitos-para-muitos você poderá selecionar qualquer lado.

Um aplicativo de web do Hibernate utiliza uma Session e uma Transaction quase do mesmo modo que um aplicativo autônomo. Entretanto, alguns modelos comuns são úteis. Nós agora criaremos um EventManagerServlet. Esse servlet lista todos os eventos salvos no banco de dados, e cria um formulário HTML para entrada de novos eventos.

Nós deveremos criar o nosso servket de processamento básico primeiramente. Uma vez que o servlet manuseia somente requisições GET do HTTP, o método que iremos implementar é doGet():

package org.hibernate.tutorial.web;


// Imports
public class EventManagerServlet extends HttpServlet {
    protected void doGet(
            HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {
        SimpleDateFormat dateFormatter = new SimpleDateFormat( "dd.MM.yyyy" );
        try {
            // Begin unit of work
            HibernateUtil.getSessionFactory().getCurrentSession().beginTransaction();
            // Process request and render page...
            // End unit of work
            HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().commit();
        }
        catch (Exception ex) {
            HibernateUtil.getSessionFactory().getCurrentSession().getTransaction().rollback();
            if ( ServletException.class.isInstance( ex ) ) {
                throw ( ServletException ) ex;
            }
            else {
                throw new ServletException( ex );
            }
        }
    }
}

Salve esse servlet como src/main/java/org/hibernate/tutorial/web/EventManagerServlet.java

O modelo que estamos aplicando neste código é chamado session-per-request. Quando uma solicitação chega ao servlet, uma nova Session do Hibernate é aberta através da primeira chamada para getCurrentSession() em SessionFactory. Então uma transação do banco de dados é inicializada e todo acesso a dados deve ocorrer dentro de uma transação, não importando se o dado é de leitura ou escrita. Não se deve utilizar o modo auto-commit em aplicações.

Nunca utilize uma nova Session do Hibernate para todas as operações de banco de dados. Utilize uma Session do Hibernate que seja de interesse à todas as solicitações. Utilize getCurrentSession(), para que seja vinculado automaticamente à thread atual de Java.

Agora, as possíveis ações de uma solicitação serão processadas e uma resposta HTML será renderizada. Já chegaremos nesta parte.

Finalmente, a unidade de trabalho termina quando o processamento e a renderização são completados. Se ocorrer algum erro durante o processamento ou a renderização, uma exceção será lançada e a transação do banco de dados revertida. Isso completa o modelo session-per-request. Em vez de usar código de demarcação de transação em todo servlet você pode também criar um filtro servlet. Dê uma olhada no website do Hibernate e do Wiki para maiores informações sobre esse modelo, chamado Sessão Aberta na Visualização. Você precisará disto assim que você considerar renderizar sua visualização no JSP, não apenas num servlet.

Vamos implementar o processamento da solicitação e renderização da página.

        // Write HTML header

        PrintWriter out = response.getWriter();
        out.println("<html><head><title>Event Manager</title></head><body>");
        // Handle actions
        if ( "store".equals(request.getParameter("action")) ) {
            String eventTitle = request.getParameter("eventTitle");
            String eventDate = request.getParameter("eventDate");
            if ( "".equals(eventTitle) || "".equals(eventDate) ) {
                out.println("<b><i>Please enter event title and date.</i></b>");
            }
            else {
                createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate));
                out.println("<b><i>Added event.</i></b>");
            }
        }
        // Print page
       printEventForm(out);
       listEvents(out, dateFormatter);
       // Write HTML footer
       out.println("</body></html>");
       out.flush();
       out.close();

O estilo deste código misturado com o Java e HTML, não escalariam em um aplicativo mais complexo, tenha em mente que estamos somente ilustrando os conceitos básicos do Hibernate neste tutorial. O código imprime um cabeçalho e nota de rodapé em HTML. Dentro desta página, são impressos um formulário para entrada de evento em HTML e uma lista de todos os evento no banco de dados. O primeiro método é trivial e somente produz um HTML:

    private void printEventForm(PrintWriter out) {

        out.println("<h2>Add new event:</h2>");
        out.println("<form>");
        out.println("Title: <input name='eventTitle' length='50'/><br/>");
        out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>");
        out.println("<input type='submit' name='action' value='store'/>");
        out.println("</form>");
    }

O método listEvents() utiliza a Session do Hibernate, limitado ao thread atual para executar uma consulta:

    private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) {


        List result = HibernateUtil.getSessionFactory()
                .getCurrentSession().createCriteria(Event.class).list();
        if (result.size() > 0) {
            out.println("<h2>Events in database:</h2>");
            out.println("<table border='1'>");
            out.println("<tr>");
            out.println("<th>Event title</th>");
            out.println("<th>Event date</th>");
            out.println("</tr>");
            Iterator it = result.iterator();
            while (it.hasNext()) {
                Event event = (Event) it.next();
                out.println("<tr>");
                out.println("<td>" + event.getTitle() + "</td>");
                out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>");
                out.println("</tr>");
            }
            out.println("</table>");
        }
    }

Finalmente, a ação store, é despachada ao método createAndStoreEvent(), que também utiliza a Session da thread atual:

    protected void createAndStoreEvent(String title, Date theDate) {

        Event theEvent = new Event();
        theEvent.setTitle(title);
        theEvent.setDate(theDate);
        HibernateUtil.getSessionFactory()
                .getCurrentSession().save(theEvent);
    }

O servlet está completo agora. Uma solicitação ao servlet será processada com uma única Session e Transaction. Quanto antes estiver no aplicativo autônomo, maior a chance do Hibernate vincular automaticamente estes objetos à thread atual de execução. Isto lhe dá a liberdade para inserir seu código e acessar a SessionFactory como desejar. Geralmente, usaríamos um diagrama mais sofisticado e moveríamos o código de acesso de dados para os objetos de acesso dos dados (o modelo DAO). Veja o Hibernate Wiki para mais exemplos.

Para implementar este aplicativo em testes, nós devemos criar um Arquivo da Web (WAR). Primeiro, nós devemos definir o descritor WAR como src/main/webapp/WEB-INF/web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
    xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <servlet>
        <servlet-name>Event Manager</servlet-name>
        <servlet-class>org.hibernate.tutorial.web.EventManagerServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Event Manager</servlet-name>
        <url-pattern>/eventmanager</url-pattern>
    </servlet-mapping>
</web-app>

Para construir e implementar, chame seu diretório de projeto ant war e copie o arquivo hibernate-tutorial.war para seu diretório Tomcat webapp.

Nota

If you do not have Tomcat installed, download it from http://tomcat.apache.org/ and follow the installation instructions. Our application requires no changes to the standard Tomcat configuration.

Uma vez implementado e com o Tomcat rodando, acesse o aplicativo em http://localhost:8080/hibernate-tutorial/eventmanager. Tenha a certeza de observar o log do Tomcat para ver o Hibernate inicializar quando a primeira solicitação chegar em seu servlet (o inicializador estático no HibernateUtil é chamado) e para obter o resultado detalhado caso exceções aconteçam.

Este tutorial cobriu itens básicos de como escrever um aplicativo Hibernate autônomo simples e um aplicativo da web pequeno. A partir do Hibernate website você poderá encontrar mais tutoriais disponíveis.