2012-01-12 18 views
10

Estamos utilizando Hibernate como capa de persistencia y tenemos un modelo de objetos complejo. Sin exponer el modelo de datos real, quiero explicar el problema usando el siguiente ejemplo simple.entidad de ahorro con entidades dependientes a las que se hace referencia usando hibernación

class Person { 
    private Integer id; //PK 
    private String name; 
    private Account account; 
    // other data, setters, getters 
} 


class Account { 
    private Integer id; //PK 
    // other data, setters, getters 
} 

El mapeo DB se define utilizando HBM como siguiente:

<class name="Person" table="PERSON"> 
    <id name="id" column="ID"> 
     <generator class="native"/> 
    </id> 
    <version name="version" type="java.lang.Long"/> 
    <property name="name" type="java.lang.String" length="50" column="NAME"/> 
    <many-to-one name="account" column="ACCOUNT_ID" 
       class="com.mycompany.model.Account"/> 

</class> 

Tengo que salvar nueva instancia poblada de Person vinculado a existente Account. La llamada es originada por el cliente web, por lo que en mi capa obtengo una instancia de Persona referenciada a la instancia de Account que solo contiene su ID.

Si trato de llamar saveOrUpdate(person) la excepción siguiente:

org.hibernate.TransientObjectException: 
object references an unsaved transient instance - save the transient instance before flushing: 
com.mycompany.model.Account 

Para evitar esto tengo que encontrar el objeto de PERSISTED Account por ID y luego llamar person.setAccount(persistedAccount). En este caso, todo funciona bien.

Pero en la vida real trato con docenas de entidades referenciadas entre sí. No quiero escribir un código especial para cada referencia.

Me pregunto si hay algún tipo de solución genérica para este problema.

Respuesta

7

a persistir una entidad, que sólo tiene que tener las referencias a sus dependencias directas. El hecho de que estas otras entidades hagan referencia a otras entidades no tiene importancia.

La mejor manera de hacerlo es obtener un proxy para la entidad a la que se hace referencia, sin siquiera presionar la base de datos, usando session.load(Account.class, accountId).

Lo que está haciendo es lo correcto: obtener una referencia a la cuenta persistente y establecer esta referencia en la cuenta recién creada.

+0

Gracias, @JB Nizet. Esto es realmente lo que esperaba ... Publicaré aquí la descripción de mi solución genérica y estaré encantado de conocer su opinión. – AlexR

1

Uso cascade="all" en la -a- * * mapeo

+0

No hay una asignación de * a muchos en la pregunta ... –

+0

@Bozho, agregué 'cascade'. En realidad, me olvidé de mencionar que ya jugué con eso. Ahora, cuando trato de salvar a una persona con una cuenta transitoria que tenga una identificación legal, recibo una excepción como la siguiente: 'org.hibernate.AsertificationFailure: null id in com.mycompany.model.Person entry (no vacíe la sesión después de que ocurra una excepción)) ' – AlexR

+0

@JB Nizet, estás escribiendo. Es 'many-to-one' – AlexR

0

¿Has probado cascade="save-update" en many-to-one element? Hibernate se predetermina a cascade="none" ...

+0

Lo intenté, @Nacho. No funciona correctamente Arroja otra excepción (ver mi comentario a la respuesta de Bozho. – AlexR

0

gracias por ayudarnos. Ya implementé mi propia solución genérica, pero quería saber si existen otras soluciones.

Quiero compartir contigo la idea. Llamo a las entidades a las que se hace referencia que no contienen nada excepto ID (u otro campo que se puede usar para identificar a la entidad de manera única) placeholder.

Así que, creé la anotación @Placeholder y la puse en todos los campos a los que se hace referencia. En nuestro ejemplo, está en el campo de cuenta de la clase Persona. Ya tenemos la clase llamada GenericDao que envuelve Hibernate API y tiene el método save(). Agregué otro método saveWithPlacehodlers() que hace lo siguiente. Descubre la clase de objeto dado por reflexión, encuentra todos los campos marcados con la anotación @Placeholder, encuentra los objetos en DB y llama al colocador apropiado en la entidad principal para reemplazar el marcador de posición referenciado por entidad persistente.

La anotación @Placeholder permite definir el campo que se utilizará para identificar la entidad. El valor predeterminado es id.

¿Qué opinas, chicos sobre esta solución?

+0

Esas soluciones "automáticas" deben manejarse con cuidado. Si entiendo bien sus descripciones, está resolviendo las dependencias en el método Save que maneja un objeto a la vez. está guardando una gran cantidad de objetos en una unidad de trabajo, está cargando todos los objetos dependientes uno por uno, lo que es muy ineficiente. Por lo general, las operaciones de la base de datos las realiza un repositorio o una clase de servicio, que deberían tener una buena idea de su trabajo. Pueden cargar muchas entidades de una sola vez (por ejemplo, usando una cláusula in) o también verificar si las entidades dependientes ya persisten. – oddparity

2

Hibernate le permite utilizar solo las clases de referencia con ID, no necesita hacer session.load().

Lo único importante es que si su objeto de referencia tiene VERSIÓN, debe configurarse la versión. En su caso, debe especificar la versión del objeto Cuenta.

+0

¿Está esto descrito en la documentación de Hibernate, o en cualquier otra documentación disponible? – sbrattla

+0

Te amo agregué la versión a mi clase y no pude guardarla más simplemente estableciendo la ID, perdí mucho tiempo con ese problema –

0

Hay una solución más al problema: usar el constructor predeterminado de la entidad para la que desea una referencia y establecer el id y la versión (si está versionada). Tenemos siguiente método DAO:

public <S extends T> S materialize(EId<S> entityId, Class<S> entityClass) { 
     Constructor<S> c = entityClass.getDeclaredConstructor(); 
     c.setAccessible(true); 

     S instance = c.newInstance(); 
     Fields.set(instance, "id", entityId.getId()); 
     Fields.set(instance, "version", entityId.getVersion()); 
     return instance; // Try catch omitted for brevity. 
} 

podemos utilizar este enfoque porque no utilizar la carga diferida sino que tienen 'vistas' de las entidades que se utilizan en la interfaz gráfica de usuario. Eso nos permite alejarnos de todas las uniones que Hibernate usa para llenar todas las relaciones impacientes. Las vistas siempre tienen id y versión de la entidad. Por lo tanto, podemos completar la referencia creando un objeto que Hibernate parecería no transitorio.

Intenté tanto este enfoque como el que tiene con session.load(). Ambos trabajaron bien. Veo alguna ventaja en mi enfoque, ya que Hibernate no se filtrará con sus proxies en ningún otro lugar del código. Si no se usa correctamente, obtendré el NPE en lugar de la excepción 'no hay sesión ligada al hilo'.

Cuestiones relacionadas