Hibernate.orgCommunity Documentation

Capítulo 10. Mapeamento de Herança

10.1. As três estratégias
10.1.1. Tabela por hierarquia de classes
10.1.2. Tabela por subclasse
10.1.3. Tabela por subclasse: usando um discriminador
10.1.4. Mesclar tabela por hierarquia de classes com tabela por subclasse
10.1.5. Tabela por classe concreta
10.1.6. Tabela por classe concreta usando polimorfismo implícito
10.1.7. Mesclando polimorfismo implícito com outros mapeamentos de herança
10.2. Limitações

O Hibernate suporta as três estratégias básicas de mapeamento de herança:

Além disso, o Hibernate suporta um quarto tipo de polimorfismo um pouco diferente:

É possível usar diferentes estratégias de mapeamento para diferentes ramificações da mesma hierarquia de herança. Você pode fazer uso do polimorfismo implícito para alcançá-lo através da hierarquia completa. De qualquer forma, o Hibernate não suporta a mistura de mapeamentos <subclass>, <joined-subclass> e <union-subclass> dentro do mesmo elemento raíz <class>. É possível usar, junto às estratégias, uma tabela por hierarquia e tabela por subclasse, abaixo do mesmo elemento <class>, combinando os elementos <subclass> e <join> (veja abaixo).

É possível definir mapeamentos subclass, union-subclass e joined-subclass em documentos de mapeamento separados, diretamente abaixo de hibernate-mapping. Isso permite que você estenda uma hierarquia de classes apenas adicionando um novo arquivo de mapeamento. Você deve especificar uma função extends no mapeamento da subclasse, nomeando uma superclasse previamente mapeada. Anteriormente esta característica fazia o ordenamento dos documentos de mapeamento importantes. Desde o Hibernate3, o ordenamento dos arquivos de mapeamento não importa quando usamos a palavra chave extends. O ordenamento dentro de um arquivo de mapeamento simples ainda necessita ser definido como superclasse antes de subclasse.



 <hibernate-mapping>
     <subclass name="DomesticCat" extends="Cat" discriminator-value="D">
          <property name="name" type="string"/>
     </subclass>
 </hibernate-mapping
>

A implementação de tabela por subclasse do Hibernate não necessita de coluna de discriminador. Outro mapeador objeto/relacional usa uma implementação diferente de tabela por subclasse, que necessita uma coluna com o tipo discriminador na tabela da superclasse. A abordagem escolhida pelo Hibernate é muito mais difícil de implementar, porém mais correto de um ponto de vista relacional. Se você deseja utilizar uma coluna discriminadora com a estratégia tabela por subclasse, você poderá combinar o uso de <subclass> e <join>, dessa maneira:


<class name="Payment" table="PAYMENT">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="PAYMENT_TYPE" type="string"/>
    <property name="amount" column="AMOUNT"/>
    ...
    <subclass name="CreditCardPayment" discriminator-value="CREDIT">
        <join table="CREDIT_PAYMENT">
            <key column="PAYMENT_ID"/>
            <property name="creditCardType" column="CCTYPE"/>
            ...
        </join>
    </subclass>
    <subclass name="CashPayment" discriminator-value="CASH">
        <join table="CASH_PAYMENT">
            <key column="PAYMENT_ID"/>
            ...
        </join>
    </subclass>
    <subclass name="ChequePayment" discriminator-value="CHEQUE">
        <join table="CHEQUE_PAYMENT" fetch="select">
            <key column="PAYMENT_ID"/>
            ...
        </join>
    </subclass>
</class
>

A declaração opcional fetch="select" diz ao Hibernate para não buscar os dados da subclasse ChequePayment, quando usar uma união externa pesquisando a superclasse.

Existem duas formas que poderíamos usar a respeito da estratégia de mapeamento de tabela por classe concreta. A primeira é usar <union-subclass>.


<class name="Payment">
    <id name="id" type="long" column="PAYMENT_ID">
        <generator class="sequence"/>
    </id>
    <property name="amount" column="AMOUNT"/>
    ...
    <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
        <property name="creditCardType" column="CCTYPE"/>
        ...
    </union-subclass>
    <union-subclass name="CashPayment" table="CASH_PAYMENT">
        ...
    </union-subclass>
    <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        ...
    </union-subclass>
</class
>

Três tabelas estão envolvidas para as subclasses. Cada tabela define colunas para todas as propriedades da classe, incluindo propriedades herdadas.

A limitação dessa abordagem é que se uma propriedade é mapeada na superclasse, o nome da coluna deve ser o mesmo em todas as tabelas das subclasses. A estratégia do gerador identidade não é permitida na união da herança de sub-classe. A fonte de chave primária deve ser compartilhada através de todas subclasses unidas da hierarquia.

Se sua superclasse é abstrata, mapeie-a com abstract="true". Claro, que se ela não for abstrata, uma tabela adicional (padrão para PAYMENT no exemplo acima), será necessária para segurar as instâncias da superclasse.

Uma abordagem alternativa é fazer uso de polimorfismo implícito:


<class name="CreditCardPayment" table="CREDIT_PAYMENT">
    <id name="id" type="long" column="CREDIT_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="amount" column="CREDIT_AMOUNT"/>
    ...
</class>

<class name="CashPayment" table="CASH_PAYMENT">
    <id name="id" type="long" column="CASH_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="amount" column="CASH_AMOUNT"/>
    ...
</class>

<class name="ChequePayment" table="CHEQUE_PAYMENT">
    <id name="id" type="long" column="CHEQUE_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="amount" column="CHEQUE_AMOUNT"/>
    ...
</class
>

Veja que em nenhum lugar mencionamos a interface Payment explicitamente. Note também que propriedades de Payment são mapeadas em cada uma das subclasses. Se você quiser evitar duplicação, considere usar entidades de XML (ex. [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ] na declaração do DOCTYPE e & allproperties; no mapeamento).

A desvantagem dessa abordagem é que o Hibernate não gera UNIONs de SQL quando executa pesquisas polimórficas.

Para essa estratégia, uma associação polimórfica para Payment geralmente é mapeada usando <any>.


<any name="payment" meta-type="string" id-type="long">
    <meta-value value="CREDIT" class="CreditCardPayment"/>
    <meta-value value="CASH" class="CashPayment"/>
    <meta-value value="CHEQUE" class="ChequePayment"/>
    <column name="PAYMENT_CLASS"/>
    <column name="PAYMENT_ID"/>
</any
>

Existe ainda um item a ser observado sobre este mapeamento. Como as subclasses são mapeadas em seu próprio elemento <class>, e como o Payment é apenas uma interface, cada uma das subclasses pode ser facilmente parte de uma outra hierarquia de herança! (E você ainda pode usar pesquisas polimórficas em cima da interface Payment.)


<class name="CreditCardPayment" table="CREDIT_PAYMENT">
    <id name="id" type="long" column="CREDIT_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="CREDIT_CARD" type="string"/>
    <property name="amount" column="CREDIT_AMOUNT"/>
    ...
    <subclass name="MasterCardPayment" discriminator-value="MDC"/>
    <subclass name="VisaPayment" discriminator-value="VISA"/>
</class>

<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
    <id name="id" type="long" column="TXN_ID">
        <generator class="native"/>
    </id>
    ...
    <joined-subclass name="CashPayment" table="CASH_PAYMENT">
        <key column="PAYMENT_ID"/>
        <property name="amount" column="CASH_AMOUNT"/>
        ...
    </joined-subclass>
    <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        <key column="PAYMENT_ID"/>
        <property name="amount" column="CHEQUE_AMOUNT"/>
        ...
    </joined-subclass>
</class
>

Mais uma vez, nós não mencionamos Payment explicitamente. Se nós executarmos uma pesquisa em cima da interface Payment, por exemplo, from Payment – o Hibernate retorna automaticamente instâncias de CreditCardPayment (e suas subclasses, desde que elas também implementem Payment), CashPayment e ChequePayment mas não as instâncias de NonelectronicTransaction.

Existem certas limitações para a abordagem do "polimorfismo implícito" comparada com a estratégia de mapeamento da tabela por classe concreta. Existe uma limitação um tanto menos restritiva para mapeamentos <union-subclass>.

A seguinte tabela demonstra as limitações do mapeamento de tabela por classe concreta e do polimorfismo implícito no Hibernate.