Domanda

Mi sono imbattuto in una situazione (che penso sia strana ma forse abbastanza normale) in cui utilizzo EntityManager.getReference (LObj.getClass (), LObj.getId ()) per ottenere un'entità database e quindi passare il oggetto restituito da persistere in un'altra tabella.

Quindi sostanzialmente il flusso era così:

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);
    ...
  }
}

Stavo ottenendo la seguente eccezione "java.lang.IllegalArgumentException: entità sconosciuta: com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0 "

Dopo averlo esaminato per un po ', ho finalmente capito che era perché stavo usando il metodo EntityManager.getReference () che stavo ottenendo l'eccezione sopra mentre il metodo restituiva un proxy.

Questo mi fa meravigliare, quando è consigliabile usare il metodo EntityManager.getReference () invece del metodo EntityManager.find () ?

EntityManager.getReference () genera un'eccezione EntityNotFoundException se non riesce a trovare l'entità cercata che è di per sé molto conveniente. Il metodo EntityManager.find () restituisce semplicemente null se non riesce a trovare l'entità.

Per quanto riguarda i limiti delle transazioni, mi sembra che tu debba usare il metodo find () prima di passare l'entità appena trovata a una nuova transazione. Se usi il metodo getReference (), probabilmente finiresti in una situazione simile alla mia con l'eccezione sopra.

È stato utile?

Soluzione

Di solito uso il metodo getReference quando non ho bisogno di accedere allo stato del database (intendo il metodo getter). Solo per cambiare stato (intendo metodo setter). Come dovresti sapere, getReference restituisce un oggetto proxy che utilizza una potente funzionalità chiamata controllo sporco automatico. Supponiamo che

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);
    }

}

Se chiamo il metodo find , il provider JPA, dietro le quinte, chiamerà

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Se chiamo il metodo getReference , il provider JPA, dietro le quinte, chiamerà

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

E sai perché ???

Quando chiami getReference, otterrai un oggetto proxy. Qualcosa del genere (il fornitore JPA si occupa dell'implementazione di questo 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;
    }

}

Quindi, prima del commit della transazione, il provider JPA vedrà il flag stateChanged per aggiornare OR NOT persona. Se nessuna riga viene aggiornata dopo la dichiarazione di aggiornamento, il provider JPA genererà EntityNotFoundException in base alla specifica JPA.

saluti,

Altri suggerimenti

Come ho spiegato in questo articolo , supponendo che tu abbia un genitore Posta e un PostComment figlio come illustrato nel seguente diagramma:

 inserisci qui la descrizione dell'immagine

Se chiami trova quando tenti di impostare l'associazione @ManyToOne post :

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

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

entityManager.persist(comment);

Hibernate eseguirà le seguenti dichiarazioni:

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)

Questa volta la query SELECT è inutile perché non è necessario recuperare l'entità Post. Vogliamo solo impostare la colonna Chiave esterna post_id sottostante.

Ora, se invece usi getReference :

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

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

entityManager.persist(comment);

Questa volta, Hibernate emetterà solo l'istruzione INSERT:

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

A differenza di find , il getReference restituisce solo un proxy di entità che ha solo l'identificatore impostato. Se accedi al proxy, l'istruzione SQL associata verrà attivata finché EntityManager è ancora aperto.

Tuttavia, in questo caso, non è necessario accedere al proxy dell'entità. Vogliamo solo propagare la chiave esterna nel record della tabella sottostante, quindi caricare un proxy è sufficiente per questo caso d'uso.

Quando si carica un proxy, è necessario tenere presente che è possibile lanciare LazyInitializationException se si tenta di accedere al riferimento proxy dopo la chiusura di EntityManager. Per maggiori dettagli sulla gestione del LazyInitializationException , controlla questo articolo .

Poiché un riferimento è "gestito", ma non idratato, può anche consentire di rimuovere un'entità tramite ID, senza dover prima caricarlo in memoria.

Poiché non è possibile rimuovere un'entità non gestita, è semplicemente sciocco caricare tutti i campi utilizzando find (...) o createQuery (...), solo per eliminarlo immediatamente.

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

Questo mi fa meravigliare, quando è consigliabile usare il   Metodo EntityManager.getReference () anziché il metodo   Metodo EntityManager.find ()?

EntityManager.getReference () è davvero un metodo soggetto a errori e ci sono davvero pochissimi casi in cui un codice client deve usarlo.
Personalmente, non ho mai avuto bisogno di usarlo.

EntityManager.getReference () e EntityManager.find (): nessuna differenza in termini di costi generali

Non sono d'accordo con la risposta accettata e in particolare:

  

Se chiamo il metodo find , il provider JPA, dietro le quinte, chiamerà

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?
     

Se chiamo il metodo getReference , il provider JPA, dietro le quinte, lo farà   chiamare

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Non è il comportamento che ottengo con Hibernate 5 e il javadoc di getReference () non dice una cosa del genere:

  

Ottieni un'istanza, il cui stato può essere recuperato pigramente. Se richiesto   l'istanza non esiste nel database, EntityNotFoundException   viene generato al primo accesso allo stato dell'istanza. (La persistenza   il runtime del provider è autorizzato a lanciare EntityNotFoundException   quando viene chiamato getReference.) L'applicazione non dovrebbe aspettarselo   lo stato dell'istanza sarà disponibile al momento del distacco, a meno che non lo fosse   accessibile dall'applicazione mentre il gestore entità era aperto.

EntityManager.getReference () risparmia una query per recuperare l'entità in due casi:

1) se l'entità è archiviata nel contesto di Persistenza, ovvero  la cache di primo livello.
E questo comportamento non è specifico di EntityManager.getReference () , EntityManager.find () risparmierà anche una query per recuperare l'entità se l'entità è memorizzata nel contesto di persistenza.

Puoi controllare il primo punto con qualsiasi esempio.
Puoi anche fare affidamento sull'attuale implementazione di Hibernate.
In effetti, EntityManager.getReference () si basa sul metodo createProxyIfNecessary () della classe org.hibernate.event.internal.DefaultLoadEventListener per caricare la classe entità.
Ecco la sua implementazione:

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 interessante è:

Object existing = persistenceContext.getEntity( keyToLoad );

2) Se non manipoliamo efficacemente l'entità, facendo eco al pigramente recuperato del javadoc.
Infatti, per garantire l'effettivo caricamento dell'entità, è necessario invocare un metodo su di essa.
Quindi il guadagno sarebbe correlato a uno scenario in cui vogliamo caricare un'entità senza la necessità di usarla? Nel quadro delle applicazioni, questa esigenza è davvero rara e inoltre il comportamento getReference () è anche molto fuorviante se leggi la parte successiva.

Perché preferire EntityManager.find () rispetto a EntityManager.getReference ()

In termini di overhead, getReference () non è migliore di find () come discusso nel punto precedente.
Quindi perché usare l'uno o l'altro?

Richiamare getReference () può restituire un'entità pigramente recuperata.
Qui, il recupero pigro non si riferisce alle relazioni dell'entità ma all'entità stessa.
Significa che se invochiamo getReference () e quindi il contesto di persistenza viene chiuso, l'entità potrebbe non essere mai caricata e quindi il risultato è davvero imprevedibile. Ad esempio, se l'oggetto proxy è serializzato, è possibile ottenere un riferimento null come risultato serializzato o se viene richiamato un metodo sull'oggetto proxy, viene generata un'eccezione come LazyInitializationException .

Significa che il lancio di EntityNotFoundException è il motivo principale per utilizzare getReference () per gestire un'istanza che non esiste nel database poiché una situazione di errore potrebbe non essere mai eseguito mentre l'entità non esiste.

EntityManager.find () non ha l'ambizione di lanciare EntityNotFoundException se l'entità non viene trovata. Il suo comportamento è sia semplice che chiaro. Non avrai mai sorpresa poiché restituisce sempre un'entità caricata o null (se l'entità non viene trovata) ma mai un'entità sotto forma di proxy che potrebbe non essere caricata in modo efficace.
Quindi EntityManager.find () dovrebbe essere preferito nella maggior parte dei casi.

Non sono d'accordo con la risposta selezionata, e come ha giustamente sottolineato davidxxx, getReference non fornisce questo comportamento di aggiornamenti dinamici senza selezionare. Ho posto una domanda relativa alla validità di questa risposta, vedere qui - impossibile aggiornare senza emettere la selezione sull'uso di setter dopo getReference () di ibernazione JPA .

Onestamente non ho visto nessuno che abbia effettivamente usato quella funzionalità. IN QUALUNQUE POSTO. E non capisco perché sia ??così votato.

Ora prima di tutto, qualunque cosa tu chiami su un oggetto proxy ibernato, un setter o un getter, viene generato un SQL e l'oggetto viene caricato.

Ma poi ho pensato, quindi se il proxy getReference () di JPA non fornisse quella funzionalità. Posso semplicemente scrivere il mio proxy.

Ora, possiamo tutti sostenere che le selezioni sulle chiavi primarie sono rapide quanto una query può ottenere e che non è davvero qualcosa da fare per evitare. Ma per quelli di noi che non riescono a gestirlo a causa di un motivo o di un altro, di seguito è riportata un'implementazione di tale proxy. Ma prima che tu veda l'implementazione, vedi il suo utilizzo e quanto sia semplice da usare.

Utilizzo

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

E questo genererebbe la seguente query -

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

e anche se si desidera inserire, è comunque possibile eseguire PersistenceService.save (nuovo ordine (" a " ;, 2)); e sparerebbe un inserto come dovrebbe.

ATTUAZIONE

Aggiungi questo al tuo pom.xml -

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

Crea questa classe per creare proxy dinamici -

@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());
    }
}

Crea un'interfaccia con tutti i metodi -

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

Ora crea un intercettore che ti permetterà di implementare questi metodi sul tuo 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!");
}
}

E la classe di eccezione -

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

Un servizio da salvare usando questo 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);
    }

}
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top