java - example - jpql find
Cuándo usar EntityManager.find() vs EntityManager.getReference() (3)
Esto me hace preguntarme, ¿cuándo es aconsejable utilizar el método EntityManager.getReference () en lugar del método EntityManager.find ()?
EntityManager.getReference()
es realmente un método propenso a errores y realmente hay muy pocos casos donde un código de cliente necesita usarlo.
Personalmente, nunca tuve que usarlo.
EntityManager.getReference () y EntityManager.find (): no hay diferencia en términos de sobrecarga
No estoy de acuerdo con la respuesta aceptada y particularmente:
Si llamo a find method, el 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 , proveedor de JPA, detrás de las escenas, llamaré
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:
Obtenga una instancia, cuyo estado puede ser extraído de forma perezosa. Si la instancia solicitada no existe en la base de datos, la EntityNotFoundException se lanza cuando se accede por primera vez al estado de la instancia. (El tiempo de ejecución del proveedor de persistencia permite lanzar EntityNotFoundException cuando se llama a getReference). La aplicación no debe esperar que el estado de instancia esté disponible al momento de la separación, a menos que la aplicación lo haya accedido mientras el administrador de entidades estaba abierto.
EntityManager.getReference()
ahorra una consulta para recuperar la entidad solo si la entidad está almacenada en el contexto de persistencia, que es la memoria caché de primer nivel.
Y este comportamiento no es específico de EntityManager.getReference()
, EntityManager.find()
también EntityManager.find()
una consulta para recuperar la entidad si la entidad está almacenada en el contexto de persistencia.
Puedes verificar el primer punto con cualquier ejemplo.
También puede confiar en la implementación real de Hibernate.
De hecho, EntityManager.getReference()
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;
}
La parte interesante es:
Object existing = persistenceContext.getEntity( keyToLoad );
Vemos que la preservación de recuperación de entidad solo se obtiene a medida que la entidad se almacena en el contexto de persistencia.
Por qué favor EntityManager.find () sobre EntityManager.getReference ()
En términos de sobrecarga, getReference()
no es mejor que find()
como se discutió en el punto anterior.
Entonces, ¿por qué usar el uno o el otro?
La invocación de getReference()
puede devolver una entidad recuperada de forma perezosa.
Aquí, la recuperación perezosa no se refiere a las relaciones de la entidad, sino a la entidad misma.
Significa que si invocamos getReference()
y luego se cierra el contexto de persistencia, la entidad nunca se cargará y 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 nunca se puede realizar mientras la entidad no existe.
EntityManager.find()
no tiene la ambición de lanzar EntityNotFoundException
si no se encuentra la entidad. Su comportamiento es simple y claro. Nunca tendrá sorpresa ya que siempre devuelve una entidad cargada o null
(si no se encuentra la entidad) pero nunca una entidad con la forma de un proxy que no se puede cargar efectivamente.
Así que EntityManager.find()
debería ser favorecido en la mayoría de los casos.
Me encontré con una situación (que creo que es extraña, pero posiblemente sea bastante normal) en la que utilizo EntityManager.getReference (LObj.getClass (), LObj.getId ()) para obtener una entidad de base de datos y luego paso el objeto devuelto a persistir en otra mesa.
Entonces, básicamente, el flujo fue 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); ... } }
Recibía la siguiente excepción "java.lang.IllegalArgumentException: entidad desconocida: com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0"
Después de analizarlo por un tiempo, finalmente descubrí que era porque estaba usando el método EntityManager.getReference () que estaba obteniendo la excepción anterior ya que el método devolvía un proxy.
Esto me hace preguntarme, ¿ cuándo es aconsejable utilizar el método EntityManager.getReference () en lugar del método EntityManager.find () ?
EntityManager.getReference () arroja una EntityNotFoundException si no puede encontrar la entidad que se busca, que es muy conveniente en sí misma. El método EntityManager.find () simplemente devuelve null si no puede encontrar la entidad.
Con respecto a los límites de transacción, me parece que necesitaría usar el método find () antes de pasar la entidad recién descubierta a una nueva transacción. Si usa el método getReference (), probablemente terminaría en una situación similar a la mía con la excepción anterior.
Debido a que una referencia es ''administrada'', pero no está 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 absurdo cargar todos los campos usando find (...) o createQuery (...), solo para eliminarlo de inmediato.
MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);
Usualmente 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 deberías saber, getReference devuelve un objeto proxy que utiliza una poderosa función llamada comprobación automática sucia. Supongamos que el 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 a find method, el 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 , proveedor de JPA, detrás de las escenas, llamaré
UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
Y usted sabe por qué ???
Cuando llame a getReference, obtendrá un objeto proxy. Algo como esto (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;
}
}
Por lo tanto, antes de la confirmación de la transacción, el proveedor de JPA verá la bandera stateChanged para actualizar o NO la entidad de la persona. Si no se actualizan filas después de la declaración de actualización, el proveedor de JPA arrojará EntityNotFoundException de acuerdo con la especificación de JPA.
Saludos,