Hibernate.orgCommunity Documentation
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).
Existem diversas variedades de mapeamento que podem ser gerados para as coleções, cobrindo muitos modelos relacionais comuns. Sugerimos que você faça o teste com a ferramenta de geração do esquema para obter uma idéia de como diversas declarações de mapeamento traduzem as tabelas de banco de dados.
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="propertyName" table="tab
le_name" schema="sc
hema_name" lazy="true
|extra|false" inverse="t
rue|false" cascade="a
ll|none|save-update|delete|all-delete-orphan|delete-orphan" sort="unso
rted|natural|comparatorClass" order-by="
column_name asc|desc" where="arb
itrary sql where condition" fetch="joi
n|select|subselect" batch-size
="N" access="fi
eld|property|ClassName" optimistic
-lock="true|false" mutable="t
rue|false" node="element-name|." embed-xml="true|false" > <key .... /> <map-key .... /> <element .... /> </map >
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
Instâncias de coleção são distinguidas no banco de dados pela chave exterior da entidade que possui a coleção. Esta chave exterior é referida como a coluna de chave de coleção (ou colunas) da tabela de coleção. A coluna de chave de coleção é mapeada pelo elemento <key>
.
Pode existir uma restrição de nulabilidade na coluna da chave exterior. Para a maioria das coleções, isto está implícito. Para associações unidirecionais um-para-muitos, a coluna de chave estrangeira é anulável por padrão, portanto você pode precisar especificar not-null="true"
.
<key column="productSerialNumber" not-null="true"/>
A restrição da chave exterior pode usar ON DELETE CASCADE
.
<key column="productSerialNumber" on-delete="cascade"/>
Veja nos capítulos anteriores para uma completa definição do elemento<key>
.
As coleções podem conter quase qualquer outro tipo de Hibernate, incluindo todos os tipos básicos, tipos padronizados, e é claro, referências a outras entidades. Isto é uma distinção importante: um objeto em uma coleção pode ser manipulada com as semânticas "valor" (seu ciclo de vida depende totalmente do proprietário da coleção), ou ele pode ser uma referência à outra entidade, com seu próprio ciclo de vida. No último caso, somente o "link" entre os dois objetos é considerado como estado seguro pela coleção.
O tipo contido é referido como tipo de elemento de coleção. Os elementos de coleção são mapeados pelo <element>
ou <composite-element>
,ou no caso de referências de entidade, com <one-to-many>
ou<many-to-many>
. Os primeiros dois, mapeiam elementos com semânticas de valor, os dois outros são usados para mapear associações de entidade.
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="column_name" base="
0|1|..."/>
| |
|
<map-key column="column_name" formul
a="any SQL expression" type="
type_name" node="@attribute-name" length="N"/>
| |
| |
|
<map-key-many-to-many column="column_name" formul
a="any SQL expression" class="ClassName" />
| |
| |
|
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="column_name" formul
a="any SQL expression" type="
typename" length="L" precision="P" scale="S" not-null="true|false" unique="true|false" node="element-name" />
| |
| |
|
A many-to-many association is specified using the <many-to-many>
element.
<many-to-many column="column_name" formul
a="any SQL expression" class=
"ClassName" fetch=
"select|join" unique
="true|false" not-fo
und="ignore|exception" entity
-name="EntityName" proper
ty-ref="propertyNameFromAssociatedClass" node="element-name" embed-xml="true|false" />
| |
| |
| |
| |
| |
| |
| |
|
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 instância de classes entidades contidas, podem não pertencer à mais de uma instância da coleção.
Uma instância da classe de entidade contida pode não aparecer em mais de um valor do índice da coleção.
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="ClassName" not-fo
und="ignore|exception" entity
-name="EntityName" node="element-name" embed-xml="true|false" />
| |
| |
|
Note que o elemento <one-to-many>
não precisa declarar qualquer coluna. Nem é necessário especificar o nome da table
em qualquer lugar.
Se a coluna da chave exterior de uma associação <one-to-many>
for declarada como NOT NULL
, você deve declarar a <key>
mapeando not-null="true"
ou use uma associação bidirecional com o mapeamento da coleção marcado como inverse="true"
. Veja a discussão das associações bidirecionais mais tarde neste mesmo capítulo.
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
>
Note que o valor da função order-by
é uma ordenação SQL e não uma ordenação.
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:
conjunto ou bag de valor em um dos lados, valor único do outro
Conjunto ou bag com valor em ambos os lados
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.
Há três meios possíveis de se mapear uma associação ternária. Uma é usar um Map
com uma associação como seu índice:
<map name="contracts">
<key column="employer_id" not-null="true"/>
<map-key-many-to-many column="employee_id" class="Employee"/>
<one-to-many class="Contract"/>
</map
>
<map name="connections">
<key column="incoming_node_id"/>
<map-key-many-to-many column="outgoing_node_id" class="Node"/>
<many-to-many column="connection_id" class="Connection"/>
</map
>
A segunda maneira é simplesmente remodelar a associação das classes da entidade. Esta é a abordagem que utilizamos com mais freqüência.
Uma alternativa final é usar os elementos compostos, que nós discutiremos mais tarde.
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.
Copyright © 2004 Red Hat, Inc.