Pregunta

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

Así que 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í la siguiente excepción " java.lang.IllegalArgumentException: Entidad desconocida: com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0 "

Después de verlo 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 pensar, ¿cuándo es recomendable usar el método EntityManager.getReference () en lugar del método EntityManager.find () ?

EntityManager.getReference () lanza una excepción EntityNotFoundException si no puede encontrar la entidad que se está buscando, lo cual es muy conveniente en sí mismo. El método EntityManager.find () simplemente devuelve un nulo si no puede encontrar la entidad.

Con respecto a los límites de la transacción, 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 (), probablemente terminarás en una situación similar a la mía con la excepción anterior.

¿Fue útil?

Solución

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 de estado (me refiero al método de establecimiento). Como debe saber, getReference devuelve un objeto proxy que utiliza una potente función llamada comprobación automática de errores. 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 al método buscar , el proveedor de JPA, entre bambalinas, 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, entre bambalinas, llamará

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

¿Y sabes por qué?

Cuando llames a getReference, obtendrás 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 confirmar la transacción, el proveedor de JPA verá el indicador stateChanged para actualizar O NO entidad de persona. Si no se actualizan filas después de la declaración de actualización, el proveedor de JPA lanzará la excepción EntityNotFoundException de acuerdo con la especificación de JPA.

saludos,

Otros consejos

Como expliqué en este artículo , suponiendo que tenga un principal Publicar la entidad y un elemento secundario PostComment como se ilustra en el siguiente diagrama:

 introduce la descripción de la imagen aquí

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

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 declaraciones:

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 buscada. Solo queremos establecer la columna de clave foránea post_id subyacente.

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 declaración INSERT:

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

A diferencia de find , getReference solo devuelve un Proxy de entidad que solo tiene el conjunto de identificadores. Si accede al Proxy, la instrucción SQL asociada se activará mientras el EntityManager esté abierto.

Sin embargo, en este caso, no necesitamos acceder al Proxy de la entidad. Solo queremos propagar la clave foránea al registro de la tabla subyacente para que la carga de un proxy sea suficiente para este caso de uso.

Al cargar un Proxy, debe tener en cuenta que se puede generar una LazyInitializationException si intenta acceder a la referencia del Proxy después de que se cierre el EntityManager. Para obtener más detalles sobre el manejo de LazyInitializationException , consulte este artículo .

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

Como no puedes eliminar una entidad no administrada, es simplemente una tontería cargar todos los campos usando find (...) o createQuery (...), solo para eliminarlo de inmediato.

MyLargeObject myObject = em.getReference(MyLargeObject.class, objectId);
em.remove(myObject);
  

Esto me hace preguntarme, ¿cuándo es aconsejable usar el   EntityManager.getReference () en lugar del método   ¿EntityManager.find () método?

EntityManager.getReference () es realmente un método propenso a errores y hay muy pocos casos en los que 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 en particular:

  

Si llamo al método buscar , el proveedor de JPA, entre bambalinas, 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, entre bambalinas, lo hará   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:

  

Obtener una instancia, cuyo estado puede ser recuperado perezosamente. Si el solicitado   instancia no existe en la base de datos, la EntityNotFoundException   se lanza cuando se accede por primera vez al estado de instancia. (La persistencia   El tiempo de ejecución del proveedor tiene permitido lanzar la excepción EntityNotFoundException   cuando se llama a getReference.) La aplicación no debe esperar que   el estado de instancia estará disponible en el desapego, a menos que fuera   Se accede a la aplicación mientras el administrador de entidades estaba abierto.

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

1) si la entidad está almacenada 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 se almacena 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 () se basa en el método createProxyIfNecessary () de la clase org.hibernate.event.internal.DefaultLoadEventListener para cargar el 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 );

2) Si no manipulamos efectivamente la entidad, enviamos un eco al perezoso obtenido del javadoc.
De hecho, para garantizar la carga efectiva de la entidad, se requiere invocar un método.
Entonces, ¿la ganancia estaría relacionada con un escenario en el que queremos cargar una entidad sin tener la necesidad de usarla? 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 a EntityManager.find () sobre EntityManager.getReference ()

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

La invocación de getReference () puede devolver una entidad de búsqueda perezosa.
Aquí, la recuperación perezosa no se refiere a las relaciones de la entidad sino a la entidad en sí.
Significa que si invocamos getReference () y luego el contexto de persistencia se cierra, la entidad nunca se cargará y, por lo tanto, el resultado es realmente impredecible. Por ejemplo, si el objeto proxy está serializado, podría obtener una referencia de null como resultado serializado o si se invoca un método en el objeto proxy, se lanza una excepción como LazyInitializationException .

Significa que la tirada 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 se realizará mientras la entidad no exista.

EntityManager.find () no tiene la ambición de lanzar EntityNotFoundException si no se encuentra la entidad. Su comportamiento es a la vez simple y claro. Nunca te sorprenderás, 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 pueda cargar de manera efectiva.
Entonces, EntityManager.find () debe ser favorecido en la mayoría de los casos.

No estoy de acuerdo con la respuesta seleccionada y, como señaló correctamente davidxxx, getReference no proporciona dicho comportamiento de actualizaciones dinámicas sin seleccionar. Hice una pregunta sobre la validez de esta respuesta, consulte aquí - no se puede actualizar sin emitir, seleccione el uso de setter after getReference () de hibernate JPA .

Honestamente, no he visto a nadie que haya usado esa funcionalidad. EN CUALQUIER SITIO. Y no entiendo por qué está tan a favor.

En primer lugar, independientemente de lo que llame a un objeto proxy de hibernación, un configurador o un captador, se dispara un SQL y se carga el objeto.

Pero luego pensé, y qué pasa si el proxy JPA getReference () no proporciona esa funcionalidad. Solo puedo escribir mi propio proxy.

Ahora, todos podemos argumentar que las selecciones en las claves primarias son tan rápidas como una consulta puede ser y no es realmente algo a lo que se debe evitar. Pero para aquellos de nosotros que no podemos manejarlo debido a una razón u otra, a continuación se muestra una implementación de dicho proxy. Pero antes de que vea la implementación, vea su uso y lo sencillo que es usarlo.

USAGE

Order example = ProxyHandler.getReference(Order.class, 3);
example.setType("ABCD");
example.setCost(10);
PersistenceService.save(example);

Y esto dispararía la siguiente consulta -

UPDATE Order SET type = 'ABCD' and cost = 10 WHERE id = 3;

e incluso si desea insertar, aún puede hacer PersistenceService.save (nueva Orden (" a " ;, 2)); y dispararía un inserto como debería.

IMPLEMENTATION

Agregue esto a su pom.xml -

<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.10</version>
</dependency>

Haz esta clase para crear proxy dinámico -

@SuppressWarnings("unchecked")
public class ProxyHandler {

public static <T> T getReference(Class<T> classType, Object id) {
    if (!classType.isAnnotationPresent(Entity.class)) {
        throw new ProxyInstantiationException("This is not an entity!");
    }

    try {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(classType);
        enhancer.setCallback(new ProxyMethodInterceptor(classType, id));
        enhancer.setInterfaces((new Class<?>[]{EnhancedProxy.class}));
        return (T) enhancer.create();
    } catch (Exception e) {
        throw new ProxyInstantiationException("Error creating proxy, cause :" + e.getCause());
    }
}

Haga una interfaz con todos los métodos -

public interface EnhancedProxy {
    public String getJPQLUpdate();
    public HashMap<String, Object> getModifiedFields();
}

Ahora, haga un interceptor que le permita implementar estos métodos en su proxy -

import com.anil.app.exception.ProxyInstantiationException;
import javafx.util.Pair;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import javax.persistence.Id;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* @author Anil Kumar
*/
public class ProxyMethodInterceptor implements MethodInterceptor, EnhancedProxy {

private Object target;
private Object proxy;
private Class classType;
private Pair<String, Object> primaryKey;
private static HashSet<String> enhancedMethods;

ProxyMethodInterceptor(Class classType, Object id) throws IllegalAccessException, InstantiationException {
    this.classType = classType;
    this.target = classType.newInstance();
    this.primaryKey = new Pair<>(getPrimaryKeyField().getName(), id);
}

static {
    enhancedMethods = new HashSet<>();
    for (Method method : EnhancedProxy.class.getDeclaredMethods()) {
        enhancedMethods.add(method.getName());
    }
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    //intercept enhanced methods
    if (enhancedMethods.contains(method.getName())) {
        this.proxy = obj;
        return method.invoke(this, args);
    }
    //else invoke super class method
    else
        return proxy.invokeSuper(obj, args);
}

@Override
public HashMap<String, Object> getModifiedFields() {
    HashMap<String, Object> modifiedFields = new HashMap<>();
    try {
        for (Field field : classType.getDeclaredFields()) {

            field.setAccessible(true);

            Object initialValue = field.get(target);
            Object finalValue = field.get(proxy);

            //put if modified
            if (!Objects.equals(initialValue, finalValue)) {
                modifiedFields.put(field.getName(), finalValue);
            }
        }
    } catch (Exception e) {
        return null;
    }
    return modifiedFields;
}

@Override
public String getJPQLUpdate() {
    HashMap<String, Object> modifiedFields = getModifiedFields();
    if (modifiedFields == null || modifiedFields.isEmpty()) {
        return null;
    }
    StringBuilder fieldsToSet = new StringBuilder();
    for (String field : modifiedFields.keySet()) {
        fieldsToSet.append(field).append(" = :").append(field).append(" and ");
    }
    fieldsToSet.setLength(fieldsToSet.length() - 4);
    return "UPDATE "
            + classType.getSimpleName()
            + " SET "
            + fieldsToSet
            + "WHERE "
            + primaryKey.getKey() + " = " + primaryKey.getValue();
}

private Field getPrimaryKeyField() throws ProxyInstantiationException {
    for (Field field : classType.getDeclaredFields()) {
        field.setAccessible(true);
        if (field.isAnnotationPresent(Id.class))
            return field;
    }
    throw new ProxyInstantiationException("Entity class doesn't have a primary key!");
}
}

Y la clase de excepción -

public class ProxyInstantiationException extends RuntimeException {
public ProxyInstantiationException(String message) {
    super(message);
}

Un servicio para guardar usando este proxy -

@Service
public class PersistenceService {

@PersistenceContext
private EntityManager em;

@Transactional
private void save(Object entity) {
    // update entity for proxies
    if (entity instanceof EnhancedProxy) {
        EnhancedProxy proxy = (EnhancedProxy) entity;
        Query updateQuery = em.createQuery(proxy.getJPQLUpdate());
        for (Entry<String, Object> entry : proxy.getModifiedFields().entrySet()) {
            updateQuery.setParameter(entry.getKey(), entry.getValue());
        }
        updateQuery.executeUpdate();
    // insert otherwise
    } else {
        em.persist(entity);
    }

}
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top