Hibernate.orgCommunity Documentation

Capítulo 7. Mapeos de asociación

7.1. Introducción
7.2. Asociaciones Unidireccionales
7.2.1. Many-to-one
7.2.2. Uno-a-uno
7.2.3. Uno-a-muchos
7.3. Asociaciones unidireccionales con tablas de unión
7.3.1. Uno-a-muchos
7.3.2. Many-to-one
7.3.3. Uno-a-uno
7.3.4. Muchos-a-muchos
7.4. Asociaciones bidireccionales
7.4.1. uno-a-muchos / muchos-a-uno
7.4.2. Uno-a-uno
7.5. Asociaciones bidireccionales con tablas de unión
7.5.1. uno-a-muchos / muchos-a-uno
7.5.2. uno a uno
7.5.3. Muchos-a-muchos
7.6. Mapeos de asociación más complejos

Los mapeos de asociación son frecuentemente lo más difícil de implementar correctamente. En esta sección revisaremos algunos casos canónicos uno por uno, comenzando con los mapeos unidireccionales y luego considerando los casos bidireccionales. Vamos a utilizar Person y Address en todos los ejemplos.

Vamos a clasificar las asociaciones en cuanto su multiplicidad y a si mapean o no a una tabla de unión interviniente.

Las claves foráneas que aceptan valores nulos no se consideran como una buena práctica en el modelado tradicional de datos, así que todos nuestros ejemplos utilizan claves foráneas no nulas. Esto no es un requisito de Hibernate y todos los mapeos funcionarán si quita las restricciones de nulabilidad.

Una asociación bidireccional muchos-a-uno es el tipo de asociación más común. El siguiente ejemplo ilustra la relación estándar padre/hijo.


<class name="Person">
    <id name="id" column="personId">
        <generator class="native"/>
    </id>
    <many-to-one name="address" 
        column="addressId"
        not-null="true"/>
</class>

<class name="Address">
    <id name="id" column="addressId">
        <generator class="native"/>
    </id>
    <set name="people" inverse="true">
        <key column="addressId"/>
        <one-to-many class="Person"/>
    </set>
</class
>
create table Person ( personId bigint not null primary key, addressId bigint not null )
create table Address ( addressId bigint not null primary key )
        

Si utiliza un List, u otra colección con índice, configure la columna key de la clave foránea como not null. Hibernate administrará la asociación del lado de las colecciones para mantener el índice de cada elemento, haciendo del otro lado virtualmente inverso al establecer update="false" y insert="false":


<class name="Person">
   <id name="id"/>
   ...
   <many-to-one name="address"
      column="addressId"
      not-null="true"
      insert="false"
      update="false"/>
</class>

<class name="Address">
   <id name="id"/>
   ...
   <list name="people">
      <key column="addressId" not-null="true"/>
      <list-index column="peopleIdx"/>
      <one-to-many class="Person"/>
   </list>
</class
>

Es importante que defina not-null="true" en el elemento <key> del mapeo de la colección si la columna de la clave foránea es NOT NULL. No declare sólamente not-null="true" en un elemento <column> posiblemente anidado sino en el elemento <key>.

Uniones de asociación más complejas son extremadamente raras. Hibernate maneja situaciones más complejas utilizando fragmentos SQL incluidos en el documento de mapeo. Por ejemplo, si una tabla con datos históricos de información de cuenta define las columnas accountNumber, effectiveEndDate y effectiveStartDate, se mapearían así:


<properties name="currentAccountKey">
    <property name="accountNumber" type="string" not-null="true"/>
    <property name="currentAccount" type="boolean">
        <formula
>case when effectiveEndDate is null then 1 else 0 end</formula>
    </property>
</properties>
<property name="effectiveEndDate" type="date"/>
<property name="effectiveStateDate" type="date" not-null="true"/>

Entonces puede mapear una asociación a la instancia actual, la que tiene effectiveEndDate nulo, utilizando:


<many-to-one name="currentAccountInfo"
        property-ref="currentAccountKey"
        class="AccountInfo">
    <column name="accountNumber"/>
    <formula
>'1'</formula>
</many-to-one
>

En un ejemplo más complejo, imagínese que la asociación entre Employee y Organization se mantienen en una tabla Employment llena de datos históricos de empleo. Entonces se puede mapear una asociación al empleador más reciente del empleado, el que tiene la startDate más reciente, de esta manera:


<join>
    <key column="employeeId"/>
    <subselect>
        select employeeId, orgId 
        from Employments 
        group by orgId 
        having startDate = max(startDate)
    </subselect>
    <many-to-one name="mostRecentEmployer" 
            class="Organization" 
            column="orgId"/>
</join
>

Esta funcionalidad le permite cierto grado de creatividad y flexibilidad, pero usualmente es más práctico manejar esta clase de casos utilizando HQL o una petición de criterio.