Hibernate.orgCommunity Documentation

Capítulo 24. Exemplo: Pai/Filho

24.1. Uma nota sobre as coleções
24.2. Bidirecional um-para-muitos
24.3. Ciclo de vida em Cascata
24.4. Cascatas e unsaved-value
24.5. Conclusão

Uma das primeiras coisas que um usuário tenta fazer com o Hibernate é modelar um tipo de relacionamento Pai/Filho. Existem duas abordagens diferentes para isto. Por diversas razões diferentes, a abordagem mais conveniente, especialmente para novos usuários, é modelar ambos os Parent e Child como classes de entidade com uma associação <one-to-many> a partir do Parent para o Child. A abordagem alternativa é declarar o Child como um <composite-element>. As semânticas padrões da associação um para muitos (no Hibernate), são muito menos parecidas com as semânticas comuns de um relacionamento pai/filho do que aqueles de um mapeamento de elemento de composição. Explicaremos como utilizar uma associação bidirecional um para muitos com cascatas para modelar um relacionamento pai/filho de forma eficiente e elegante.

As coleções do Hibernate são consideradas uma parte lógica de suas próprias entidades, nunca das entidades contidas. Saiba que esta é uma distinção que possui as seguintes conseqüências:

A adição de uma entidade à coleção, por padrão, meramente cria um link entre as duas entidades. A remoção da entidade, removerá o link. Isto é muito apropriado para alguns tipos de casos. No entanto, não é apropriado o caso de um relacionamento pai/filho. Neste caso, a vida do filho está vinculada ao ciclo de vida do pai.

Suponha que começamos uma associação <one-to-many> simples de Parent para Child.


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

Se fossemos executar o seguinte código:

Parent p = .....;

Child c = new Child();
p.getChildren().add(c);
session.save(c);
session.flush();

O Hibernate editaria duas instruções SQL

Isto não é somente ineficiente como também viola qualquer restrição NOT NULL na coluna parent_id. Nós podemos concertar a violação da restrição de nulabilidade, especificando um not-null="true" no mapeamento da coleção:


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

No entanto, esta não é uma solução recomendada.

As causas subjacentes deste comportamento é que o link (a chave exterior parent_id) de p para c não é considerada parte do estado do objeto Child e por isso não é criada no INSERT. Então a solução é fazer uma parte de link do mapeamento do Child.


<many-to-one name="parent" column="parent_id" not-null="true"/>

Nós também precisamos adicionar a propriedade parent à classe do Child.

Agora que a entidade Child está gerenciando o estado do link, informaremos à coleção para não atualizar o link. Utilizamos o atributo inverse para isto:


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

O seguinte código seria usado para adicionar um novo Child:

Parent p = (Parent) session.load(Parent.class, pid);

Child c = new Child();
c.setParent(p);
p.getChildren().add(c);
session.save(c);
session.flush();

E agora, somente um SQL INSERT seria editado.

Para assegurar tudo isto, podemos criar um método de addChild() do Parent.

public void addChild(Child c) {

    c.setParent(this);
    children.add(c);
}

Agora, o código que adiciona um Child se parece com este:

Parent p = (Parent) session.load(Parent.class, pid);

Child c = new Child();
p.addChild(c);
session.save(c);
session.flush();

A chamada explícita para save() ainda é incômoda. Iremos nos referir a ela utilizando cascatas.


<set name="children" inverse="true" cascade="all">
    <key column="parent_id"/>
    <one-to-many class="Child"/>
</set
>

Isto simplifica o código acima para:

Parent p = (Parent) session.load(Parent.class, pid);

Child c = new Child();
p.addChild(c);
session.flush();

Da mesma forma, não precisamos repetir este comando com os filhos ao salvar ou deletar um Parent. O comando seguinte irá remover o p e todos os seus filhos do banco de dados.

Parent p = (Parent) session.load(Parent.class, pid);

session.delete(p);
session.flush();

No entanto, este código:

Parent p = (Parent) session.load(Parent.class, pid);

Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
c.setParent(null);
session.flush();

não irá remover c do banco de dados. Neste caso, ele somente removerá o link para p e causará uma violação de restrição NOT NULL). Você precisará delete() de forma explícita o Child.

Parent p = (Parent) session.load(Parent.class, pid);

Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();

Agora, no seu caso, um Child não pode existir sem seu pai. Então, se removermos um Child da coleção, não iremos mais querer que ele seja deletado. Devido a isto, devemos utilizar um cascade="all-delete-orphan".


<set name="children" inverse="true" cascade="all-delete-orphan">
    <key column="parent_id"/>
    <one-to-many class="Child"/>
</set
>

Apesar do mapeamento da coleção especificar inverse="true", as cascatas ainda são processadas por repetição dos elementos de coleção. Portanto, se você requiser que um objeto seja salvo, deletado ou atualizado por uma cascata, você deverá adicioná-lo à sua coleção. Chamar setParent() não é o bastante.

Suppose we loaded up a Parent in one Session, made some changes in a UI action and wanted to persist these changes in a new session by calling update(). The Parent will contain a collection of children and, since the cascading update is enabled, Hibernate needs to know which children are newly instantiated and which represent existing rows in the database. We will also assume that both Parent and Child have generated identifier properties of type Long. Hibernate will use the identifier and version/timestamp property value to determine which of the children are new. (See Seção 11.7, “Detecção automática de estado”.) In Hibernate3, it is no longer necessary to specify an unsaved-value explicitly.

O seguinte código atualizará o parent e o child e inserirá um newChild:

//parent and child were both loaded in a previous session

parent.addChild(child);
Child newChild = new Child();
parent.addChild(newChild);
session.update(parent);
session.flush();

Bem, isto cabe bem no caso de um identificador gerado, mas e os identificadores atribuídos e os identificadores de composição? Isto é mais difícil, pois uma vez que o Hibernate não pode utilizar a propriedade do identificador para distinguir entre um objeto instanciado recentemente, com um identificador atribuído pelo usuário, e um objeto carregado em uma sessão anterior. Neste caso, o Hibernate usará tanto um carimbo de data e hora (timestamp) ou uma propriedade de versão, ou irá na verdade consultar um cache de segundo nível, ou no pior dos casos, o banco de dados, para ver se a linha existe.

Há muito o que digerir aqui e pode parecer confuso na primeira vez. No entanto, na prática, funciona muito bem. A maioria dos aplicativos do Hibernate utiliza o modelo pai/filho em muitos lugares.

Nós mencionamos uma alternativa neste primeiro parágrafo. Nenhum dos casos acima existem no caso de mapeamentos <composite-element>, que possuem exatamente a semântica do relacionamento pai/filho. Infelizmente, existem duas grandes limitações para elementos compostos: elementos compostos podem não possuir coleções e assim sendo podem não ser filhos de nenhuma outra entidade a não ser do pai único.