Cuándo usar EntityManager.find () vs EntityManager.getReference () con JPA


Me he encontrado con una situación (que creo que es rara, pero posiblemente es bastante normal) donde uso el EntityManager.getReference (LObj.getClass (), LObj.getId ()) para obtener una entidad de base de datos y luego pasar el objeto devuelto para que se conserve en otra tabla.

Así que básicamente el flujo era así:

class TFacade{

  createT(FObj, AObj) {
    T TObj = new T();
    TObj.setF(FObj);
    TObj.setA(AObj);
    ...
    EntityManager.persist(TObj);
    ...
    L LObj = A.getL();
    FObj.setL(LObj);
    FFacade.editF(FObj);
  }
}

@TransactionAttributeType.REQUIRES_NEW
class FFacade{

  editF(FObj){
    L LObj = FObj.getL();
    LObj = EntityManager.getReference(LObj.getClass(), LObj.getId());
    ...
    EntityManager.merge(FObj);
    ...
    FLHFacade.create(FObj, LObj);
  }
}

@TransactionAttributeType.REQUIRED
class FLHFacade{

  createFLH(FObj, LObj){
    FLH FLHObj = new FLH();
    FLHObj.setF(FObj);
    FLHObj.setL(LObj);
    ....
    EntityManager.persist(FLHObj);
    ...
  }
}

Estaba recibiendo la siguiente excepción "java.lang.IllegalArgumentException: Unknown entity: com.my.persistence. L Enhanc EnhancerByCGLIB 3 3e7987d0 "

Después de investigar por un tiempo, finalmente me di cuenta de que era porque estaba usando el EntityManager.Método getReference () que estaba obteniendo la excepción anterior ya que el método estaba devolviendo un proxy.

Esto me hace preguntarme, cuando es aconsejable usar el EntityManager.Método getReference () en lugar del EntityManager.método find () ?

EntityManager.getReference() lanza una EntityNotFoundException si no puede encontrar la entidad que se está buscando, lo que es muy conveniente en sí mismo. EntityManager.el método find () simplemente devuelve null si no puede encontrar la entidad.

Con respecto a los límites de las transacciones, me parece que necesitaría usar el método find() antes de pasar la entidad recién encontrada a una nueva transacción. Si usas el método getReference() entonces probablemente terminarías en una situación similar a la mía con la excepción anterior.

Author: Vlad Mihalcea, 2009-10-22

4 answers

Normalmente uso el método getReference cuando no necesito acceder al estado de la base de datos (me refiero al método getter). Solo para cambiar el estado (me refiero al método setter). Como debe saber, getReference devuelve un objeto proxy que utiliza una poderosa característica llamada comprobación sucia automática. Supongamos lo siguiente

public class Person {

    private String name;
    private Integer age;

}


public class PersonServiceImpl implements PersonService {

    public void changeAge(Integer personId, Integer newAge) {
        Person person = em.getReference(Person.class, personId);

        // person is a proxy
        person.setAge(newAge);
    }

}

Si llamo find método, proveedor de JPA, detrás de las escenas, llamará

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Si llamo al método getReference , el proveedor de JPA, detrás de las escenas, call

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Y sabes por qué ???

Cuando llame a getReference, obtendrá un objeto proxy. Algo como este (el proveedor de JPA se encarga de implementar este proxy)

public class PersonProxy {

    // JPA provider sets up this field when you call getReference
    private Integer personId;

    private String query = "UPDATE PERSON SET ";

    private boolean stateChanged = false;

    public void setAge(Integer newAge) {
        stateChanged = true;

        query += query + "AGE = " + newAge;
    }

}

Así que antes de la confirmación de la transacción, el proveedor de JPA verá el indicador stateChanged para actualizar O NO la entidad person. Si no se actualiza ninguna fila después de la instrucción update, el proveedor de JPA lanzará EntityNotFoundException de acuerdo con la especificación de JPA.

Saludos,

 136
Author: Arthur Ronald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-10-22 17:14:47

Debido a que una referencia es 'administrada', pero no hidratada, también puede permitirle eliminar una entidad por ID, sin necesidad de cargarla primero en la memoria.

Como no puede eliminar una entidad no administrada, es simplemente tonto cargar todos los campos usando find(...) o createQuery(...), solo para eliminarlo inmediatamente.

MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);
 7
Author: Steven Spungin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-07 15:09:05

Como expliqué en este artículo , suponiendo que usted tiene una entidad padre Post y un hijo PostComment como se ilustra en el siguiente diagrama:

introduzca la descripción de la imagen aquí

Si llama a find cuando intenta establecer el @ManyToOne post asociación:

PostComment comment = new PostComment();
comment.setReview("Just awesome!");

Post post = entityManager.find(Post.class, 1L);
comment.setPost(post);

entityManager.persist(comment);

Hibernate ejecutará las siguientes instrucciones:

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_
FROM   post p
WHERE p.id = 1

INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)

La consulta SELECT es inútil esta vez porque no necesitamos que la entidad Post sea recuperada. Solo queremos establecer la Clave externa post_id subyacente columna.

Ahora, si usas getReference en su lugar:

PostComment comment = new PostComment();
comment.setReview("Just awesome!");

Post post = entityManager.getReference(Post.class, 1L);
comment.setPost(post);

entityManager.persist(comment);

Esta vez, Hibernate emitirá solo la instrucción INSERT: {[16]]}

INSERT INTO post_comment (post_id, review, id)
VALUES (1, 'Just awesome!', 1)

A diferencia de find, el getReference solo devuelve un Proxy de entidad que solo tiene el identificador establecido. Si accede al proxy, la instrucción SQL asociada se activará mientras el EntityManager siga abierto.

Sin embargo, en este caso, no necesitamos acceder al Proxy de entidad. Solo queremos propagar la Clave Foránea a la tabla subyacente record so cargando un proxy es suficiente para este caso de uso.

Al cargar un Proxy, debe tener en cuenta que se puede lanzar una excepción LazyInitializationException si intenta acceder a la referencia del proxy después de que el EntityManager esté cerrado. Para obtener más detalles sobre cómo manejar el LazyInitializationException, consulte este artículo.

 3
Author: Vlad Mihalcea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-20 09:52:10

Esto me hace preguntarme, cuando es aconsejable utilizar el EntityManager.Método getReference () en lugar del EntityManager.método find ()?

EntityManager.getReference() es realmente un método propenso a errores y realmente hay muy pocos casos en los que un código de cliente necesita usarlo.
Personalmente, nunca necesité usarlo.

EntityManager.getReference () y EntityManager.find (): no hay diferencia en términos de sobrecarga

No estoy de acuerdo con la respuesta aceptada y en particular:

Si llamo find método, proveedor de JPA, detrás de las escenas, llamará

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Si llamo al método getReference , el proveedor de JPA, detrás de las escenas, call

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

No es el comportamiento que obtengo con Hibernate 5 y el javadoc de getReference() no dice tal cosa:

Obtener una instancia, cuyo estado puede ser buscada perezosamente. Si el pedido instancia no existe en la base de datos, el EntityNotFoundException se lanza cuando se accede por primera vez al estado de la instancia. (Persistencia se permite el tiempo de ejecución del proveedor para lanzar la EntityNotFoundException cuando se llama getReference. La aplicación no debe esperar que el estado de la instancia estará disponible en el momento del desprendimiento, a menos que accedido por la aplicación mientras el entity manager estaba abierto.

EntityManager.getReference() ahorra una consulta para recuperar la entidad en dos casos :

1) si la entidad se almacena en el Contexto de persistencia, es decir el caché de primer nivel.
Y este comportamiento no es específico de EntityManager.getReference(), EntityManager.find() también ahorrará una consulta para recuperar la entidad si la entidad está almacenada en el contexto de Persistencia.

Puede comprobar el primer punto con cualquier ejemplo.
También puede confiar en la implementación real de Hibernación.
De hecho, EntityManager.getReference() se basa en el método createProxyIfNecessary() de la clase org.hibernate.event.internal.DefaultLoadEventListener para cargar la entidad.
Aquí está su implementación :

private Object createProxyIfNecessary(
        final LoadEvent event,
        final EntityPersister persister,
        final EntityKey keyToLoad,
        final LoadEventListener.LoadType options,
        final PersistenceContext persistenceContext) {
    Object existing = persistenceContext.getEntity( keyToLoad );
    if ( existing != null ) {
        // return existing object or initialized proxy (unless deleted)
        if ( traceEnabled ) {
            LOG.trace( "Entity found in session cache" );
        }
        if ( options.isCheckDeleted() ) {
            EntityEntry entry = persistenceContext.getEntry( existing );
            Status status = entry.getStatus();
            if ( status == Status.DELETED || status == Status.GONE ) {
                return null;
            }
        }
        return existing;
    }
    if ( traceEnabled ) {
        LOG.trace( "Creating new proxy for entity" );
    }
    // return new uninitialized proxy
    Object proxy = persister.createProxy( event.getEntityId(), event.getSession() );
    persistenceContext.getBatchFetchQueue().addBatchLoadableEntityKey( keyToLoad );
    persistenceContext.addProxy( keyToLoad, proxy );
    return proxy;
}

El interesante parte es :

Object existing = persistenceContext.getEntity( keyToLoad );

2) Si no manipulamos efectivamente la entidad, haciendo eco al perezosamente buscado del javadoc.
De hecho, para garantizar la carga efectiva de la entidad, se requiere invocar un método en ella.
Así que la ganancia estaría relacionado a un escenario donde queremos cargar una entidad sin tener la necesidad de usarlo ? En el marco de las aplicaciones, esta necesidad es realmente poco común y además el comportamiento getReference() también es muy engañoso si lee la siguiente parte.

Por qué favorecer EntityManager.find () sobre EntityManager.getReference ()

En términos de gastos generales, getReference() no es mejor que find() como se discutió en el punto anterior.
Entonces, ¿por qué usar uno u otro ?

Invocar getReference() puede devolver una entidad buscada perezosamente.
Aquí, la búsqueda perezosa no se refiere a las relaciones de la entidad, sino a la propia entidad.
Esto significa que si invocamos getReference() y luego el contexto de Persistencia está cerrado, la entidad puede ser nunca cargado y así el resultado es realmente impredecible. Por ejemplo, si el objeto proxy está serializado, puede obtener una referencia null como resultado serializado o si se invoca un método en el objeto proxy, se lanza una excepción como LazyInitializationException.

Significa que el lanzamiento de EntityNotFoundException que es la razón principal para usar getReference() para manejar una instancia que no existe en la base de datos como una situación de error puede nunca realizarse mientras la entidad no existe.

EntityManager.find() no tiene la ambición de lanzar EntityNotFoundException si la entidad no se encuentra. Su comportamiento es simple y claro. Nunca tendrá sorpresa, ya que siempre devuelve una entidad cargada o null (si la entidad no se encuentra), pero nunca una entidad bajo la forma de un proxy que puede no ser efectivamente cargada.
Así que EntityManager.find() debe ser favorecido en la mayoría de los casos.

 2
Author: davidxxx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-28 06:18:34