Hibernate.orgCommunity Documentation

Capítulo 6. Mapeamento de coleção

6.1. Coleções persistentes
6.2. Mapeamento de coleção
6.2.1. Chaves Externas de Coleção
6.2.2. Elementos de coleção
6.2.3. Coleções indexadas
6.2.4. Coleções de valores e associações muitos-para-muitos
6.2.5. Associações um-para-muitos
6.3. Mapeamentos de coleção avançados.
6.3.1. Coleções escolhidas
6.3.2. Associações Bidirecionais
6.3.3. Associações bidirecionais com coleções indexadas
6.3.4. Associações Ternárias
6.3.5. Using an <idbag>
6.4. Exemplos de coleções

O Hibernate requer que os campos de coleções de valor persistente sejam declarados como um tipo de interface. Por exemplo:

public class Product {

    private String serialNumber;
    private Set parts = new HashSet();
    
    public Set getParts() { return parts; }
    void setParts(Set parts) { this.parts = parts; }
    public String getSerialNumber() { return serialNumber; }
    void setSerialNumber(String sn) { serialNumber = sn; }
}

A interface atual pode ser java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap ou o que desejar. ("o que desejar" significa que você terá que escrever uma implementação de org.hibernate.usertype.UserCollectionType.)

Observe como inicializamos a variável da instância com uma instância de HashSet. Esta é a melhor maneira de inicializar propriedades de coleções de valor de instâncias recentemente instanciadas (não persistentes). Quando você fizer uma instância persistente, chamando persist(), como por exemplo: o Hibernate substituirá o HashSet por uma instância da própria implementação do Hibernate do Set. Cuidado com erros como este:

Cat cat = new DomesticCat();

Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.persist(cat);
kittens = cat.getKittens(); // Okay, kittens collection is a Set
(HashSet) cat.getKittens(); // Error!

As coleções persistentes injetadas pelo Hibernate, se comportam como HashMap, HashSet, TreeMap, TreeSet ou ArrayList, dependendo do tipo de interface.

As instâncias de coleção têm o comportamento comum de tipos de valores. Eles são automaticamente persistidos quando referenciados por um objeto persistente e automaticamente deletados quando não referenciados. Se a coleção é passada de um objeto persistente para outro, seus elementos devem ser movidos de uma tabela para outra. Duas entidades não devem compartilhar uma referência com uma mesma instância de coleção. Devido ao modelo relacional adjacente, as propriedades de coleções válidas, não suportam semânticas de valores nulos. O Hibernate não distingue entre a referência da coleção nula e uma coleção vazia.

Use as coleções persistentes da mesma forma que usa coleções Java comuns. No entanto, somente tenha a certeza de entender as semânticas de associações bidirecionais (as quais serão discutidas mais tarde).

O elemento do mapeamento do Hibernate, usado para mapear uma coleção, depende do tipo de interface. Por exemplo, um elemento <set> é usado para mapear propriedades do tipo Set.

<class name="Product">

    <id name="serialNumber" column="productSerialNumber"/>
    <set name="parts">
        <key column="productSerialNumber" not-null="true"/>
        <one-to-many class="Part"/>
    </set>
</class
>

Além do <set>, existe também os elementos de mapeamento <list>, <map>, <bag>, <array> and <primitive-array>. O elemento <map> é de representação:

<map
    name="prop(1)ertyName"
    table="tab(2)le_name"
    schema="sc(3)hema_name"
    lazy="true(4)|extra|false"
    inverse="t(5)rue|false"
    cascade="a(6)ll|none|save-update|delete|all-delete-orphan|delete-orphan"
    sort="unso(7)rted|natural|comparatorClass"
    order-by="(8)column_name asc|desc"
    where="arb(9)itrary sql where condition"
    fetch="joi(10)n|select|subselect"
    batch-size(11)="N"
    access="fi(12)eld|property|ClassName"
    optimistic(13)-lock="true|false"
    mutable="t(14)rue|false"
    node="element-name|."
    embed-xml="true|false"
>

    <key .... />
    <map-key .... />
    <element .... />
</map
>

1

name: o nome da propriedade da coleção

2

table (opcional - padrão para nome de propriedade): o nome da tabela de coleção. Isto não é usado para associações um-para-muitos.

3

schema (opcional): o nome de um esquema de tabela para sobrescrever o esquema declarado no elemento raíz.

4

lazy (opcional - padrão para true): pode ser utilizado para desabilitar a busca lazy e especificar que a associação é sempre buscada antecipadamente, ou para habilitar busca "extra-lazy" onde a maioria das operações não inicializa a coleção (apropriado para coleções bem grandes).

5

inverse (opcional - padrão para false): marque esta coleção como o lado "inverso" de uma associação bidirecional.

6

cascade (opcional - padrão para none): habilita operações para cascata para entidades filho.

7

sort (opcional): especifica uma coleção escolhida com ordem de escolhanatural ou uma dada classe comparatória.

8

order-by (opcional, somente JDK1.4): especifica uma coluna da tabela (ou colunas) que define a ordem de iteração do Map, Set ou bag, juntos com um asc ou desc opcional.

9

where (opcional): especifica uma condição SQL arbitrária WHERE a ser usada quando recuperar ou remover a coleção Isto é útil se a coleção tiver somente um subconjunto dos dados disponíveis.

10

fetch (opcional, padrão para select): escolha entre busca de união externa, busca por seleção sequencial e busca por subseleção sequencial.

11

batch-size (opcional, padrão para 1): especifica um "tamanho de lote" para instâncias de busca lazy desta coleção.

12

access (opcional - padrão para property): A estratégia que o Hibernate deve usar para acessar a coleção de valor de propriedade.

13

optimistic-lock (opcional - padrão para true): especifica que alterações para o estado da coleção, resulta no incremento da versão da própria entidade. Para associações um-para-muitos, é sempre bom desabilitar esta configuração.

14

mutable (opcional - padrão para true): um valor de false especifica que os elementos da coleção nunca mudam. Isto permite uma otimização mínima do desempenho em alguns casos.

Todos os mapeamentos de coleção, exceto aqueles com semânticas de conjunto e bag, precisam de uma coluna índice na tabela de coleção, uma coluna que mapeia para um índice matriz ou índice List ou chave de Map. O índice de um Map pode ser de qualquer tipo, mapeado com <map-key>, pode ser uma referência de entidade mapeada com <map-key-many-to-many>, ou pode ser um tipo composto, mapeado com <composite-map-key>. O índice de uma matriz ou lista é sempre do tipo integer e é mapeado usando o elemento <list-index>. As colunas mapeadas contém inteiros sequenciais, dos quais são numerados a partir do zero, por padrão.

<list-index
        column(1)="column_name"
        base="(2)0|1|..."/>

1

column_name (required): the name of the column holding the collection index values.

1

base (optional - defaults to 0): the value of the index column that corresponds to the first element of the list or array.

<map-key
        column(1)="column_name"
        formul(2)a="any SQL expression"
        type="(3)type_name"
        node="@attribute-name"
        length="N"/>

1

column (optional): the name of the column holding the collection index values.

2

formula (optional): a SQL formula used to evaluate the key of the map.

3

type (required): the type of the map keys.

<map-key-many-to-many
        column(1)="column_name"
        formul(2)(3)a="any SQL expression"
        class="ClassName"
/>

1

column (optional): the name of the foreign key column for the collection index values.

2

formula (optional): a SQ formula used to evaluate the foreign key of the map key.

3

class (required): the entity class used as the map key.

Se sua tabela não possui uma coluna de índice e você ainda quiser usar a Lista como tipo de propriedade, você deve mapeiar a propriedade como uma <bag> do Hibernate. Uma bag não mantém sua ordem quando é recuperadada do banco de dados, mas pode ser escolhida de forma opcional ou ordenada.

Quaisquer valores de coleção ou associação muitos-para-muitos requerem uma tabela de coleção dedicada, com uma coluna de chave exterior ou colunas, collection element column ou colunas e possivelmente uma coluna de índice ou colunas.

Para uma coleção com valores, utilizamos a tag <element>. Por exemplo:

<element
        column(1)="column_name"
        formul(2)a="any SQL expression"
        type="(3)typename"
        length="L"
        precision="P"
        scale="S"
        not-null="true|false"
        unique="true|false"
        node="element-name"
/>

1

column (optional): the name of the column holding the collection element values.

2

formula (optional): an SQL formula used to evaluate the element.

3

type (required): the type of the collection element.

A many-to-many association is specified using the <many-to-many> element.

<many-to-many
        column(1)="column_name"
        formul(2)a="any SQL expression"
        class=(3)"ClassName"
        fetch=(4)"select|join"
        unique(5)="true|false"
        not-fo(6)und="ignore|exception"
        entity(7)-name="EntityName"
        proper(8)ty-ref="propertyNameFromAssociatedClass"
        node="element-name"
        embed-xml="true|false"
    />

1

column (optional): the name of the element foreign key column.

2

formula (optional): an SQL formula used to evaluate the element foreign key value.

3

class (required): the name of the associated class.

4

fetch (optional - defaults to join): enables outer-join or sequential select fetching for this association. This is a special case; for full eager fetching in a single SELECT of an entity and its many-to-many relationships to other entities, you would enable join fetching,not only of the collection itself, but also with this attribute on the <many-to-many> nested element.

5

unique (optional): enables the DDL generation of a unique constraint for the foreign-key column. This makes the association multiplicity effectively one-to-many.

6

not-found (optional - defaults to exception): specifies how foreign keys that reference missing rows will be handled: ignore will treat a missing row as a null association.

7

entity-name (optional): the entity name of the associated class, as an alternative to class.

8

property-ref (optional): the name of a property of the associated class that is joined to this foreign key. If not specified, the primary key of the associated class is used.

Segue abaixo alguns exemplos.

Um conjunto de strings:


<set name="names" table="person_names">
    <key column="person_id"/>
    <element column="person_name" type="string"/>
</set
>

Uma bag contendo inteiros com uma ordem de iteração determinada pelo atributo order-by):


<bag name="sizes"
        table="item_sizes" 
        order-by="size asc">
    <key column="item_id"/>
    <element column="size" type="integer"/>
</bag
>

Uma matriz de entidades, neste caso, uma associação muitos-para-muitos:


<array name="addresses"
        table="PersonAddress" 
        cascade="persist">
    <key column="personId"/>
    <list-index column="sortOrder"/>
    <many-to-many column="addressId" class="Address"/>
</array
>

Um mapa desde índices de strigs até datas:


<map name="holidays"
        table="holidays" 
        schema="dbo" 
        order-by="hol_name asc">
    <key column="id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map
>

Uma lista de componentes (isto será discutido no próximo capítulo):


<list name="carComponents"
        table="CarComponents">
    <key column="carId"/>
    <list-index column="sortOrder"/>
    <composite-element class="CarComponent">
        <property name="price"/>
        <property name="type"/>
        <property name="serialNumber" column="serialNum"/>
    </composite-element>
</list
>

Uma associação um para muitos liga as tabelas das duas classes através de uma chave exterior, sem a intervenção da tabela de coleção. Este mapeamento perde um pouco da semântica das coleções normais do Java:

Uma associação a partir do Produto até a Parte requer a existência de uma coluna de chave exterior e possivelmente uma coluna de índice para a tabela Part Uma tag <one-to-many> indica que esta é uma associação um para muitos.

<one-to-many
        class=(1)"ClassName"
        not-fo(2)und="ignore|exception"
        entity(3)-name="EntityName"
        node="element-name"
        embed-xml="true|false"
    />

1

class (requerido): O nome da classe associada.

2

not-found (opcional - padrão para exception): Especifica como os identificadores em cache que referenciam as linhas faltantes serão tratadas: ignore tratará a linha faltante como uma associação nula.

3

entity-name (opcional): O nome da entidade da classe associada, como uma alternativa para a class.

Note que o elemento <one-to-many> não precisa declarar qualquer coluna. Nem é necessário especificar o nome da table em qualquer lugar.

Este exemplo demonstra um mapa das entidades Part por nome, onde partName é uma propriedade persistente de Part. Note que o uso de um índice baseado em fórmula:


<map name="parts"
        cascade="all">
    <key column="productId" not-null="true"/>
    <map-key formula="partName"/>
    <one-to-many class="Part"/>
</map
>

O Hibernate suporta a implementação de coleções java.util.SortedMap e java.util.SortedSet. Você deve especificar um comparador no arquivo de mapeamento:


<set name="aliases"
            table="person_aliases" 
            sort="natural">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" sort="my.custom.HolidayComparator">
    <key column="year_id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map
>

Valores permitidos da funçãosort sãounsorted, natural e o nome de uma classe implementando java.util.Comparator.

Coleções escolhidas, na verdade se comportam como java.util.TreeSet ou java.util.TreeMap.

Se você quiser que o próprio banco de dados ordene os elementos da coleção use a função order-by do set, bag ou mapeamentos map. Esta solução está disponível somente sob JDK 1.4 ou versões posteriores e é implementada usando LinkedHashSet ou LinkedHashMap). Este desempenha a ordenação na consulta SQL, não em memória.


<set name="aliases" table="person_aliases" order-by="lower(name) asc">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" order-by="hol_date, hol_name">
    <key column="year_id"/>
    <map-key column="hol_name" type="string"/>
    <element column="hol_date type="date"/>
</map
>

Associações podem também ser escolhidas por algum critério arbritrário em tempo de espera usando uma coleção filter():

sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();

Uma associação bidirecional permite a navegação de ambos os "lados" da associação. Dois dos casos de associação bidirecional, são suportados:

Você deve especificar uma associação muitos-para-muitos bidirecional, simplesmente mapeando as duas associações muitos-para-muitos para alguma tabela de banco de dados e declarando um dos lados como inverso Voce não poderá selecionar uma coleção indexada.

Segue aqui um exemplo de uma associação muitos-para-muitos bidirecional. Cada categoria pode ter muitos ítens e cada ítem pode estar em várias categorias:


<class name="Category">
    <id name="id" column="CATEGORY_ID"/>
    ...
    <bag name="items" table="CATEGORY_ITEM">
        <key column="CATEGORY_ID"/>
        <many-to-many class="Item" column="ITEM_ID"/>
    </bag>
</class>

<class name="Item">
    <id name="id" column="ITEM_ID"/>
    ...

    <!-- inverse end -->
    <bag name="categories" table="CATEGORY_ITEM" inverse="true">
        <key column="ITEM_ID"/>
        <many-to-many class="Category" column="CATEGORY_ID"/>
    </bag>
</class
>

As mudanças feitas somente de um lado da associação não são persistidas. Isto significa que o Hibernate tem duas representações na memória para cada associação bidirecional, uma associação de A para B e uma outra associação de B para A. Isto é mais fácil de compreender se você pensa sobre o modelo de objetos do Java e como criamos um relacionamento muitos para muitos em Java:



category.getItems().add(item);          // The category now "knows" about the relationship
item.getCategories().add(category);     // The item now "knows" about the relationship
session.persist(item);                   // The relationship won't be saved!
session.persist(category);               // The relationship will be saved

A outra ponta é usada para salvar a representação em memória à base de dados.

Você pode definir uma associação bidirecional um para muitos através de uma associação um-para-muitos indicando as mesmas colunas da tabela que à associação muitos-para-um e declarando a propriedade inverse="true".


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <set name="children" inverse="true">
        <key column="parent_id"/>
        <one-to-many class="Child"/>
    </set>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        not-null="true"/>
</class
>

Mapear apenas uma das pontas da associação com inverse="true" não afeta as operações em cascata, uma vez que isto é um conceito ortogonal.

Uma associação bidirecional onde um dos lados é representado por uma <list> ou <map> requer uma consideração especial. Se houver uma propriedade da classe filha que faça o mapeamento da coluna do índice sem problemas, pode-se continuar usando inverse="true" no mapeamento da coleção:


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children" inverse="true">
        <key column="parent_id"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <property name="name" 
        not-null="true"/>
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        not-null="true"/>
</class
>

Mas, se não houver nenhuma propriedade na classe filha, não podemos ver essa associação como verdadeiramente bidirecional (há uma informação disponível em um lado da associação que não está disponível no extremo oposto). Nesse caso, nós não podemos mapear a coleção usando inverse="true". Devemos usar o seguinte mapeamento:


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children">
        <key column="parent_id"
            not-null="true"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        insert="false"
        update="false"
        not-null="true"/>
</class
>

Veja que neste mapeamento, o lado de coleção válida da associação é responsável pela atualização da chave exterior.

A maioria das associações e coleções muitos para muitos de valores apresentados anteriormente mapeiam às tabelas com as chaves de composição, mesmo que foi sugerido que as entidades devem ser identificadores sintéticos (chaves substitutas). Uma tabela de associação pura não parece tirar muito proveito de uma chave substituta, mesmo que uma coleção de valores compostos usufruam disto. É por este motivo que o Hibernate provê uma maneira de mapear uma associação muitos para muitos com uma coleção de valores para uma tabela com uma chave substituta.

O elemento <idbag> permite mapear um List (ou uma Collection) com uma semântica de bag. Por exemplo:


<idbag name="lovers" table="LOVERS">
    <collection-id column="ID" type="long">
        <generator class="sequence"/>
    </collection-id>
    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="Person" fetch="join"/>
</idbag
>

O <idbag> possui um gerador de id sintético, igual a uma classe de entidade. Uma chave substituta diferente é associada para cada elemento de coleção. Porém, o Hibernate não provê de nenhum mecanismo para descobrir qual a chave substituta de uma linha em particular.

Note que o desempenho de atualização de um <idbag> é melhor do que um <bag> normal. O Hibernate pode localizar uma linha individual eficazmente e atualizar ou deletar individualmente, como um list, map ou set.

Na implementação atual, a estratégia de geração de identificador native não é suportada para identificadores de coleção usando o <idbag>.

Esta sessão cobre os exemplos de coleções.

A seguinte classe possui uma coleção de instâncias Child:

package eg;

import java.util.Set;
public class Parent {
    private long id;
    private Set children;
    public long getId() { return id; }
    private void setId(long id) { this.id=id; }
    private Set getChildren() { return children; }
    private void setChildren(Set children) { this.children=children; }
    ....
    ....
}

Se cada Filho tiver no máximo um Pai, o mapeamento natural é uma associação um para muitos:


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping
>

Esse mapeamento gera a seguinte definição de tabelas


create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
alter table child add constraint childfk0 (parent_id) references parent

Se o pai for obrigatório, use uma associação bidirecional um para muitos:


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" inverse="true">
            <key column="parent_id"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
        <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
    </class>

</hibernate-mapping
>

Repare na restrição NOT NULL:


create table parent ( id bigint not null primary key )
create table child ( id bigint not null
                     primary key,
                     name varchar(255),
                     parent_id bigint not null )
alter table child add constraint childfk0 (parent_id) references parent

Uma outra alternativa, no caso de você insistir que esta associação deva ser unidirecional, você pode declarar a restrição como NOT NULL no mapeamento <key>:


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children">
            <key column="parent_id" not-null="true"/>
            <one-to-many class="Child"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping
>

Por outro lado, se um filho puder ter os múltiplos pais, a associação apropriada será muitos-para-muitos:


<hibernate-mapping>

    <class name="Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" table="childset">
            <key column="parent_id"/>
            <many-to-many class="Child" column="child_id"/>
        </set>
    </class>

    <class name="Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
    </class>

</hibernate-mapping
>

Definições das tabelas:

create table parent ( id bigint not null primary key )
create table child ( id bigint not null primary key, name varchar(255) )
create table childset ( parent_id bigint not null,
                        child_id bigint not null,
                        primary key ( parent_id, child_id ) )
alter table childset add constraint childsetfk0 (parent_id) references parent
alter table childset add constraint childsetfk1 (child_id) references child

For more examples and a complete explanation of a parent/child relationship mapping, see Capítulo 22, Exemplo: Pai/Filho for more information.

Até mesmo o mapeamento de associações mais complexos serão discutimos no próximo capítulo.