Relacionamentos polimórficos com hibernate. Usando o @Any

Acho engraçado quando estamos começando a estudar Rails e alguém sempre fala que uma das vantagens é que o ActiveRecord do Rails tem suporte a relacionamento polimórfico. O que muita gente não sabe é que o hibernate também tem!

Antes de mais nada o que é um relacionamento polimórfico?
Seguindo o exemplo da apostila de rails da caelum imagine que eu tenho uma entidade Comentario que nada mais é do que a opinião de um usuário sobre qualquer coisa. Um restaurante, um prato, um chefe, etc. Como representamos isso no mundo OO?
diagrama

Se não pensarmos no banco de dados, o problema já está resolvido. Criamos uma interface chamada Comentavel e basta fazer minhas entidades a implementarem que poderei relacioná-las com o Comentario. O meu Comentario TEM-UM [qualquer coisa que implemente Comentavel]

Uma das formas de resolver esse problema é anotar a interface com @Entity e usar o @Inheritance como se a interface fosse de fato uma classe mãe, mas essa é a maneira feia… A forma “correta” para esse caso é usar o @Any que não criará nenhuma tabela e terá o comportamento igual ao do ActiveRecord do Rails.
Para poder usar o @Any temos algumas outras anotações para “configurar” o relacionamento que são a @AnyMetaDef, @MetaValue e a provavelmente já conhecida @JoinColumn.
Exemplo completo:

@Entity
public class Comentario {
@Id
@GeneratedValue
private long id;

private String comentario;

@Any(metaColumn = @Column(name = "detail_type"))
@AnyMetaDef(idType = "long", metaType = "string", metaValues = {
@MetaValue(value = "RESTAURANTE", targetEntity = Restaurante.class),
@MetaValue(value = "PRATO", targetEntity = Prato.class) })
@JoinColumn(name = "detail_id")
private Comentavel comentavel;
...
}

Explicações:
O que o hibernate fará é criar 2 colunas novas na tabela, uma para o id do registro relacionado, como num Many-to-One comum, e uma segunda coluna para especificar de qual tabela que é aquele registro. Essa segunda coluna é necessária porque o relacionamento pode ser com qualquer tabela e apenas com o id seria impossível saber se o comentário é do Restaurante de id 1 ou do Prato de id 1.
A primeira coisa que é configurada é qual o tipo de dado do id. No nosso caso é o seguinte trecho de código que define @AnyMetaDef(idType = “long”
Lembrando que todas as entidades que vão se relacionar com o Comentario devem obrigatoriamente ter o mesmo tipo de dados no id. No meu exemplo, Long.
A segunda parte é configurar o que vai na segunda coluna, que é o restando do código:

@AnyMetaDef(idType = "long", metaType = "string", metaValues = {
@MetaValue(value = "RESTAURANTE", targetEntity = Restaurante.class),
@MetaValue(value = "PRATO", targetEntity = Prato.class) })

Aqui dizemos que o tipo de dado (“metaType”) dessa segunda coluna é String e quando fizer um relacionamento com um Restaurante ele colocará o valor “RESTAURANTE” nessa coluna e no caso de um Prato “PRATO”, ambos definidos com a anotação @MetaValue que dizemos qual o valor para cada Entidade.

Depois de inserir alguns registros teremos algo parecido com a tabela abaixo

Id Comentario detail_id (O nome da coluna é defino no “name” do @JoinColumn) detail_type (O nome é definido no “name” do @Column que é “metaColumn” do @Any)
1 Otimo restaurante 1 RESTAURANTE
2 Prato muito apimentado 1 PRATO
3 Faltou mais um acompanhamento 4 PRATO

Também poderíamos ter especificado como metaType, por exemplo, long e com isso teríamos que configurar os meta values com números.
Exemplo:

@AnyMetaDef(idType = "long", metaType = "long", metaValues = {
@MetaValue(value = "1", targetEntity = Restaurante.class),
@MetaValue(value = "2", targetEntity = Prato.class) })

Com essa configuração a tabela gerada teria ficado assim

Id Comentario detail_id detail_type
1 Otimo restaurante 1 1
2 Prato muito apimentado 1 2
3 Faltou mais um acompanhamento 4 2

Ainda sobre esse assunto, existe também a annotation @ManyToAny que merece ser estudada, mas esse post já ficou muito maior do que eu gostaria. Qual quer dúvida, como sempre, basta enviar comentário ou email.

Tagged with: , , , , ,
Posted in Hibernate, Java
7 comments on “Relacionamentos polimórficos com hibernate. Usando o @Any
  1. Alberto says:

    Muito legal este post!!!, eu fiz uma vc isso na mão!!!, a tabela ficou igualzinha… não sabia dessa feature do hibernate. Valeu Paniz!!

  2. Rodrigo Facholi says:

    Muito bom cabelo, vai ajudar muito.

  3. Jonatas Bastos says:

    David, estou desenvolvendo um sistema em uma linha de produto em uma disciplina do mestrado. Estamos usando Flex + Cairngorm + BlazeDS + Java + Spring + Hibernate. Como estamos desenvolvendo baseado em componentes nos encontramos com a seguinte duvida.

    Temos a classe Submission do Componente Submission e uma classe Event no Componente Event. Temos também um componente Core que se liga a cada componente. Mas temos um relacionamento entre as classes Submission e Event, pois em submission temos um atributo do tipo Event.

    Para quebrar o acoplamento entre vários componentes e um possível relacionamento cíclico decidimos colocar uma interfaçe IEvent no Core e fazer com que a classe Event do Componente Event implemente esta interface. Assim a classe Submission passa a ter um atributo do tipo IEvent event (como o componente Sbmission e Event estão ligados ao core, quebraríamos o acoplamento direto entre os componentes Submission e Event).

    O problema é como seria a maneira certa de fazer um mapeamento no hibernate utilizando um inteface. no nosso caso mapear o atibuto event da classe Submission. Tentamos fazer o que foi proposto e em outras fontes, mas a seguinte exception aparece:

    org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘sessionFactory’ defined in ServletContext resource [/WEB-INF/config/database.xml]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: @OneToOne or @ManyToOne on br.com.rise.risechair.submission.model.Submission.IEvent references an unknown entity: br.com.rise.risechair.core.api.IEvent

    Alguma sugestão, o que poderia ser????

    se for possível fornecer o seu email eu posso enviar o código referente para vc olhar…

    • David Paniz says:

      Bom dia Jonatas,

      Esse erro está acontecendo porque você está tentando usar o @OneToOne ou @ManyToOne pra uma classe (Interface no caso) que não está anotada com @Entity. Pra resolver esse problema você tem 2 opções: Ou usa o @Entity na interface e cria o schema como se tivesse usando herança mesmo (Primeiro exemplo desse post usando @Inheritance); Ou substitui a anotação no Submission pra @Any ou @ManyToAny (Lembrando que pra esse caso tem todas as configurações de meta que estão nesse post). Pra qualquer outra dúvida pode entrar em contato pelo email contato@davidpaniz.com

  4. David says:

    Boa tarde,

    Achei muito bom este post, ele foi muito útil para mim. Mas agora estou tendo um problema com consultas orientadas a objetos.
    Criei uma interface e duas classes para implementar a mesma. Gostaria de saber como fazer um select orientado a objetos da coluna definida em
    “@Any(metaColumn = @Column(name = “tipo”))”
    Estou utilizando um NamedQuery

    Se tiver uma forma de fazer isso seria ótimo pois não quero utilizar sql nativa.

    Grato desde já,

    David

    • David Paniz says:

      Oi David,

      na hql funciona normalmente utilizar o campo polimórfico. Seguindo o exemplo do post você poderia fazer, por exemplo, a seguinte query:

      “select c Comentario c where c.comentavel = :pComantavel”

  5. Renato says:

    Muito bom, resolveu o meu problema! Valeu!!! Abraco

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>