wake-up-neo.com

Wann EntityManager.find () vs EntityManager.getReference () mit JPA verwendet

Ich bin auf eine Situation gestoßen (die ich merkwürdig finde, aber möglicherweise ganz normal ist), in der ich EntityManager.getReference (LObj.getClass (), LObj.getId ()) verwende, um eine Datenbankentität abzurufen und das zurückgegebene Objekt dann zu übergeben in einem anderen Tisch bestehen bleiben.

Im Grunde war der Fluss so:

 Klasse TFacade {

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

 @ TransactionAttributeType.REQUIRES_NEW 
 Klasse FFacade {

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

 @ TransactionAttributeType.REQUIRED 
 Klasse FLHFacade {

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

Ich habe folgende Ausnahme erhalten "Java.lang.IllegalArgumentException: Unbekannte Entität: com.my.persistence.L $$ EnhancerByCGLIB $$ 3e7987d0"

Nachdem ich mich eine Weile damit befasst hatte, fand ich schließlich heraus, dass ich die obige Ausnahme bekam, weil ich die EntityManager.getReference () -Methode verwendete, da die Methode einen Proxy zurückgab.

Ich wundere mich daher, wann ist es ratsam, die EntityManager.getReference () -Methode anstelle der EntityManager.find () -Methode zu verwenden

EntityManager.getReference () löst eine EntityNotFoundException aus, wenn nicht gefunden wird, nach welcher Entität gesucht wird, was in sich selbst sehr praktisch ist. Die EntityManager.find () - Methode gibt nur null zurück, wenn sie die Entität nicht finden kann.

In Bezug auf Transaktionsgrenzen klingt das für mich so, als müssten Sie die find () - Methode verwenden, bevor Sie die neu gefundene Entität an eine neue Transaktion übergeben. Wenn Sie die getReference () -Methode verwenden, befinden Sie sich wahrscheinlich in einer ähnlichen Situation wie bei der oben genannten Ausnahme.

91
SibzTer

Ich verwende normalerweise die Methode getReference, wenn ich nicht auf den Datenbankstatus zugreifen muss (ich meine Getter-Methode). Nur um den Status zu ändern (ich meine Setter-Methode). Wie Sie wissen sollten, gibt getReference ein Proxy-Objekt zurück, das eine leistungsstarke Funktion namens automatische Dirty-Checking verwendet. Nehmen wir folgendes an

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

}

Wenn ich die Methode find anrufe, wird der JPA-Provider hinter den Kulissen anrufen

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Wenn ich die getReference -Methode anrufe, ruft der JPA-Provider hinter den Kulissen an

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Und du weißt warum ???

Wenn Sie getReference aufrufen, erhalten Sie ein Proxy-Objekt. So etwas (JPA-Provider sorgt für die Implementierung dieses 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;
    }

}

Vor dem Transaktionsfestschreiben wird dem JPA-Provider das stateChanged-Flag angezeigt, um OR NICHT Personenentität zu aktualisieren. Wenn nach der Aktualisierungsanweisung keine Zeilen aktualisiert werden, löst der JPA-Anbieter die EntityNotFoundException entsprechend der JPA-Spezifikation aus.

grüße,

137
Arthur Ronald

Da eine Referenz zwar "verwaltet", aber nicht hydratisiert ist, können Sie eine Entität anhand der ID entfernen, ohne sie zuerst in den Speicher laden zu müssen.

Da Sie eine nicht verwaltete Entität nicht entfernen können, ist es einfach dumm, alle Felder mit find (...) oder createQuery (...) zu laden, um sie sofort zu löschen.

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

Wie ich in diesem Artikel erklärt habe, vorausgesetzt, Sie haben eine übergeordnete Post-Entität und ein untergeordnetes PostComment, wie im folgenden Diagramm dargestellt:

 enter image description here

Wenn Sie find aufrufen, wenn Sie versuchen, die Assoziation @ManyToOnepost festzulegen,

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

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

entityManager.persist(comment);

Hibernate führt die folgenden Anweisungen aus:

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)

Die SELECT-Abfrage ist diesmal unbrauchbar, da die Entität "Post" nicht abgerufen werden muss. Wir möchten nur die zugrunde liegende post_id-Fremdschlüsselspalte festlegen.

Wenn Sie stattdessen getReference verwenden:

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

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

entityManager.persist(comment);

Dieses Mal gibt Hibernate nur die INSERT-Anweisung aus:

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

Im Gegensatz zu find gibt getReference nur einen Entity-Proxy zurück, für den nur der Bezeichner festgelegt ist. Wenn Sie auf den Proxy zugreifen, wird die zugehörige SQL-Anweisung ausgelöst, solange der EntityManager noch geöffnet ist.

In diesem Fall brauchen wir jedoch nicht auf die Entität Proxy zuzugreifen. Wir möchten nur den Fremdschlüssel an den zugrunde liegenden Tabellendatensatz weitergeben, sodass das Laden eines Proxys für diesen Anwendungsfall ausreichend ist.

Wenn Sie einen Proxy laden, müssen Sie wissen, dass eine LazyInitializationException ausgelöst werden kann, wenn Sie versuchen, auf die Proxy-Referenz zuzugreifen, nachdem der EntityManager geschlossen wurde. Weitere Informationen zum Umgang mit LazyInitializationException finden Sie unter diesem Artikel .

5
Vlad Mihalcea

Ich frage mich, wann es ratsam ist, die .__ zu verwenden. EntityManager.getReference () -Methode statt der EntityManager.find () -Methode?

EntityManager.getReference() ist wirklich eine fehleranfällige Methode, und in sehr wenigen Fällen muss ein Clientcode diese verwenden.
Ich persönlich habe es nie gebraucht. 

EntityManager.getReference () und EntityManager.find (): kein Unterschied hinsichtlich des Overheads

Ich stimme der akzeptierten Antwort nicht zu und insbesondere:

Wenn ich die Methode find anrufe, wird der JPA-Provider hinter den Kulissen anrufen

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Wenn ich die getReference -Methode anrufe, wird der JPA-Provider hinter den Kulissen Anruf

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Es ist nicht das Verhalten, das ich mit Hibernate 5 bekomme, und der Javadoc von getReference() sagt so etwas nicht: 

Holen Sie sich eine Instanz, deren Zustand träge abgerufen werden kann. Wenn der angeforderte Instanz ist in der Datenbank nicht vorhanden, EntityNotFoundException wird ausgelöst, wenn zum ersten Mal auf den Instanzstatus zugegriffen wird. (Die Dauer-Provider-Laufzeit des Providers kann die EntityNotFoundException Auslösen, wenn getReference aufgerufen wird.) Die Anwendung sollte das nicht erwarten Der Instanzzustand ist nach der Trennung verfügbar, es sei denn, es war Zugriff auf die Anwendung, während der Entitätsmanager geöffnet war.

EntityManager.getReference() erspart eine Abfrage zum Abrufen der Entität in zwei Fällen: 

1) Wenn die Entität im Persistence-Kontext gespeichert ist, ist das der Cache der ersten Ebene.
Dieses Verhalten ist nicht spezifisch für EntityManager.getReference(). EntityManager.find() wird auch eine Abfrage zum Abrufen der Entität erübrigen, wenn die Entität im Persistence-Kontext gespeichert ist. 

Sie können den ersten Punkt mit jedem Beispiel überprüfen.
Sie können sich auch auf die eigentliche Hibernate-Implementierung verlassen.
Tatsächlich verlässt sich EntityManager.getReference() auf die createProxyIfNecessary()-Methode der org.hibernate.event.internal.DefaultLoadEventListener-Klasse, um die Entität zu laden.
Hier ist die Implementierung:

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

Der interessante Teil ist:

Object existing = persistenceContext.getEntity( keyToLoad );

2) Wenn wir die Entität nicht effektiv manipulieren und auf das träge des Javadoc zurückgreifen.
Um das effektive Laden der Entität sicherzustellen, ist das Aufrufen einer Methode erforderlich.
Der Gewinn würde also von einem Szenario abhängen, in dem wir eine Entität laden möchten, ohne sie verwenden zu müssen? Im Rahmen von Anwendungen ist dieses Bedürfnis wirklich ungewöhnlich und außerdem ist das getReference()-Verhalten sehr irreführend, wenn Sie den nächsten Teil lesen. 

Warum lieber EntityManager.find () über EntityManager.getReference () 

In Bezug auf den Overhead ist getReference() nicht besser als find(), wie im vorigen Punkt beschrieben.
Warum also das eine oder das andere? 

Durch den Aufruf von getReference() wird möglicherweise eine langsam abgerufene Entität zurückgegeben.
Hier bezieht sich das faule Holen nicht auf Beziehungen der Entität, sondern auf die Entität selbst.
Dies bedeutet, dass, wenn wir getReference() aufrufen und der Persistence-Kontext geschlossen wird, die Entität möglicherweise nie geladen wird, sodass das Ergebnis wirklich unvorhersehbar ist. Wenn das Proxy-Objekt beispielsweise serialisiert ist, können Sie eine null-Referenz als serialisiertes Ergebnis erhalten oder wenn eine Methode für das Proxy-Objekt aufgerufen wird, wird eine Ausnahme wie LazyInitializationException ausgelöst. 

Dies bedeutet, dass der Wurf von EntityNotFoundException, der der Hauptgrund für die Verwendung von getReference() ist, um eine Instanz zu behandeln, die nicht in der Datenbank vorhanden ist, da eine Fehlersituation niemals ausgeführt werden kann, während die Entität nicht vorhanden ist. 

EntityManager.find() hat nicht den Ehrgeiz, EntityNotFoundException zu werfen, wenn die Entität nicht gefunden wird. Sein Verhalten ist einfach und klar. Sie werden nie überraschen, da immer eine geladene Entität oder null (wenn die Entität nicht gefunden wird) zurückgegeben wird, aber niemals eine Entität in der Form eines Proxy, die möglicherweise nicht effektiv geladen wird.
EntityManager.find() sollte daher in den meisten Fällen bevorzugt werden. 

3
davidxxx

Ich stimme der ausgewählten Antwort nicht zu, und wenn davidxxx richtig darauf hingewiesen hat, bietet getReference ein solches Verhalten bei dynamischen Aktualisierungen nicht ohne select. Ich habe eine Frage zur Gültigkeit dieser Antwort gestellt, siehe hier - kann nicht aktualisiert werden, ohne select on using setter nach getReference () von hibernate JPA auszugeben.

Ich habe ehrlich gesagt niemanden gesehen, der diese Funktionalität tatsächlich verwendet hat. IRGENDWO. Und ich verstehe nicht, warum es so positiv ist.

Zunächst einmal, egal, was Sie für ein Hibernate-Proxy-Objekt, einen Setter oder einen Getter aufrufen, wird eine SQL ausgelöst und das Objekt wird geladen.

Aber dann dachte ich mir, was ist, wenn JPA getReference () Proxy diese Funktionalität nicht bietet. Ich kann einfach meinen eigenen Proxy schreiben.

Nun können wir alle behaupten, dass die Auswahl von Primärschlüsseln so schnell ist, wie eine Abfrage möglich ist, und es ist nicht wirklich etwas, das zu vermeiden ist. Aber für diejenigen von uns, die aus irgendeinem Grund nicht damit umgehen können, finden Sie unten eine Implementierung eines solchen Proxy. Aber bevor Sie die Implementierung sehen, sehen Sie ihre Verwendung und wie einfach sie zu verwenden ist.

VERWENDUNGSZWECK

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

Und dies würde die folgende Abfrage auslösen -

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

und selbst wenn Sie einfügen möchten, können Sie PersistenceService.save (new Order ("a", 2)) noch ausführen. und es würde einen Einsatz feuern, wie es sollte.

IMPLEMENTIERUNG

Fügen Sie dies Ihrer pom.xml hinzu -

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

Erstellen Sie diese Klasse, um einen dynamischen Proxy zu erstellen.

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

Erstellen Sie eine Schnittstelle mit allen Methoden - 

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

Erstellen Sie nun einen Interceptor, mit dem Sie diese Methoden auf Ihrem Proxy implementieren können.

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

Und die Ausnahmeklasse - 

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

Ein Dienst, der mit diesem Proxy gespeichert werden soll - 

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

}
}
0
Anil Kumar