Frage

Ich bin auf eine Situation gestoßen (was ich für seltsam halte, aber möglicherweise ganz normal ist), in der ich die EntityManager.GetReferation (lobj.getClass (), lobj.getid ()) verwende, um eine Datenbankeinheit zu erhalten und dann das zurückgegebene Objekt an übergeben zu in einem anderen Tisch bestehen bleiben.

Im Grunde war der Fluss so:

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

Ich bekam die folgende Ausnahme "java.lang.IillegalargumentException: Unbekannte Entität: com.my.persistence.l $$ EnhancerByCglib $$ 3E7987D0"

Nachdem ich es eine Weile untersucht hatte, fand ich schließlich heraus, dass ich die methode enttityManager.getreference () verwendete, dass ich die obige Ausnahme erhielt, da die Methode einen Proxy zurückgab.

Das lässt mich wundern, Wann ist es ratsam, die Methode entityManager.getreferenz () anstelle der EntityManager.find () -Methode zu verwenden?

EntityManager.getReference () wirft eine EntityNotFoundException aus, wenn sie nicht feststellen kann, dass die Entität nach der Suche an sich ist. EntityManager.find () Methode gibt lediglich NULL zurück, wenn sie das Entität nicht finden kann.

In Bezug auf Transaktionsgrenzen klingt für mich für mich, wie Sie die Find () -Methode verwenden müssten, bevor Sie das neu gefundene Unternehmen an eine neue Transaktion übergeben. Wenn Sie die GetReference () -Methode verwenden, würden Sie wahrscheinlich in einer ähnlichen Situation mit der oben genannten Ausnahme enden.

War es hilfreich?

Lösung

Normalerweise benutze ich GetReference Methode, wenn ich keinen Datenbankstatus zugreifen muss (ich meine Getter -Methode). Nur um den Zustand zu ändern (ich meine Setter -Methode). Wie Sie wissen sollten, gibt GetReference ein Proxy -Objekt zurück, das eine leistungsstarke Funktion namens Automatic Dirty Checking verwendet. Angenommen, das Folgende

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 anrufe finden Methode, JPA -Anbieter, hinter den Kulissen, wird anrufen

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Wenn ich anrufe GetReference Methode, JPA -Anbieter, hinter den Kulissen, wird anrufen

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Und du weißt warum ???

Wenn Sie GetReference anrufen, erhalten Sie ein Proxy -Objekt. Etwas wie dieses (JPA -Anbieter kümmert sich um 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 Transaktionsausschuss wird der JPA -Anbieter das staatsangehme Flag angezeigt, um eine Person zu aktualisieren oder nicht. Wenn nach der Update -Anweisung keine Zeilen aktualisiert werden, werfen JPA -Anbieter EntityNotFoundException gemäß der JPA -Spezifikation.

Grüße,

Andere Tipps

Wie ich erklärte Dieser Artikel, Angenommen, Sie haben einen Elternteil Post Entität und ein Kind PostComment Wie im folgenden Diagramm dargestellt:

enter image description here

Wenn Sie anrufen find Wenn Sie versuchen, das festzulegen @ManyToOne post Verband:

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

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

entityManager.persist(comment);

Hibernate wird die folgenden Aussagen ausführen:

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 nutzlos, da wir die Post -Entität nicht abrufen müssen. Wir möchten nur die zugrunde liegende post_id -Fremdschlüsselspalte festlegen.

Jetzt, wenn Sie verwenden getReference stattdessen:

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 Einfügungsanweisung aus:

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

nicht wie find, das getReference Gibt nur einen Entity -Proxy zurück, bei dem nur der Kenner eingestellt 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 müssen wir jedoch nicht auf den Entity -Proxy zugreifen. Wir möchten nur den Fremdschlüssel zum zugrunde liegenden Tabellensatz verbreiten, sodass das Laden eines Proxys für diesen Anwendungsfall ausreicht.

Beim Laden eines Proxy müssen Sie sich bewusst sein, dass eine LazyInitializationException geworfen werden kann, wenn Sie versuchen, auf die Proxy -Referenz zuzugreifen, nachdem der EntityManager geschlossen wurde. Weitere Informationen zum Umgang mit dem LazyInitializationException, Kasse Dieser Artikel.

Da eine Referenz "verwaltet", aber nicht hydratisiert ist, können Sie auch eine Entität durch 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 CreateEquery (...) zu laden, nur um sie sofort zu löschen.

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

Das frage mich, wann es ratsam ist, die Methode entityManager.getreference () anstelle der Methode entityManager.find () zu verwenden?

EntityManager.getReference() Ist wirklich eine fehleranfällige Methode und es gibt wirklich nur sehr wenige Fälle, in denen ein Client -Code verwendet werden muss.
Persönlich musste ich es nie benutzen.

EntityManager.getreferenz () und entityManager.find (): Kein Unterschied in Bezug auf Overhead

Ich bin mit der akzeptierten Antwort nicht einverstanden und insbesondere:

Wenn ich anrufe finden Methode, JPA -Anbieter, hinter den Kulissen, wird anrufen

SELECT NAME, AGE FROM PERSON WHERE PERSON_ID = ?

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

Wenn ich anrufe GetReference Methode, JPA -Anbieter, hinter den Kulissen, wird anrufen

UPDATE PERSON SET AGE = ? WHERE PERSON_ID = ?

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

Holen Sie sich eine Instanz, deren Staat träge abgerufen werden kann. Wenn die angeforderte Instanz in der Datenbank nicht vorhanden ist, wird auf die EntityNotFoundException geworfen, wenn der Instanzzustand zum ersten Mal zugegriffen wird. (Die Laufzeit des Persistenzanbieters darf die EntityNotFoundException verwerfen, wenn GetReference aufgerufen wird.) Der Antrag sollte nicht erwarten, dass der Instanzstatus bei der Ablösung verfügbar ist, es sei denn, sie wurde vom Antrag auf den Entitätsmanager geöffnet.

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

1) Wenn das Entität im Persistenzkontext gespeichert ist, ist dies der erste Cache der ersten Ebene.
Und dieses Verhalten ist nicht spezifisch für EntityManager.getReference(), EntityManager.find() Ersperrt auch eine Abfrage, um das Unternehmen abzurufen, wenn das Unternehmen im Persistenzkontext gespeichert ist.

Sie können den ersten Punkt mit jedem Beispiel überprüfen.
Sie können sich auch auf die tatsächliche Hibernate -Implementierung verlassen.
In der Tat, EntityManager.getReference() stützt sich auf die createProxyIfNecessary() Methode der org.hibernate.event.internal.DefaultLoadEventListener Klasse, um die Entität zu laden.
Hier ist seine 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, wiederholt sie an die faul abgerufen des Javadoc.
Um die effektive Belastung der Entität zu gewährleisten, ist das Aufrufen einer Methode erforderlich.
Der Gewinn würde also mit einem Szenario zusammenhängen, in dem wir eine Entität laden möchten, ohne es zu verwenden? Im Anwendungsrahmen ist dieser Bedarf wirklich ungewöhnlich und zusätzlich die getReference() Das Verhalten ist auch sehr irreführend, wenn Sie den nächsten Teil lesen.

Warum enttityManager.find () gegenüber entityManager.getreferenz () bevorzugen

In Bezug auf Overhead, getReference() ist nicht besser als find() Wie im vorherigen Punkt erläutert.
Warum also den einen oder anderen verwenden?

Anrufen getReference() kann eine faul abgerufene Einheit zurückgeben.
Hier bezieht sich das faule Abrufen nicht auf Beziehungen der Entität, sondern auf die Entität selbst.
Es bedeutet, dass, wenn wir anrufen getReference() Und dann ist der Persistenzkontext geschlossen, die Entität kann nie geladen und das Ergebnis ist wirklich unvorhersehbar. Wenn das Proxy -Objekt beispielsweise serialisiert ist, können Sie eine erhalten null Referenz als serialisiertes Ergebnis oder wenn eine Methode auf das Proxy -Objekt aufgerufen wird, eine Ausnahme wie z. B. LazyInitializationException ist geworfen.

Es bedeutet, dass der Wurf von EntityNotFoundException Das ist der Hauptgrund zu verwenden getReference() Um eine Instanz zu verarbeiten, die in der Datenbank nicht als Fehlersituation vorliegt, kann nie durchgeführt werden, während das Entität nicht vorhanden ist.

EntityManager.find() hat nicht das Ehrgeiz, zu werfen EntityNotFoundException Wenn die Entität nicht gefunden wird. Sein Verhalten ist sowohl einfach als auch klar. Sie werden niemals Überraschung haben, da es immer eine geladene Einheit zurückgibt oder null (Wenn die Entität nicht gefunden wird), aber niemals eine Entität unter der Form eines Proxy, der möglicherweise nicht effektiv geladen wird.
So EntityManager.find() sollte in den meisten Fällen bevorzugt werden.

Ich bin mit der ausgewählten Antwort nicht einverstanden, und wie Davidxxx korrekt ausgewiesen hat, liefert GetReference kein solches Verhalten dynamischer Updationen ohne Auswahl. Ich stellte eine Frage zur Gültigkeit dieser Antwort, siehe hier - Ich kann nicht aktualisieren, ohne ausgewählte zu verwenden, um Setter nach GetReference () von Hibernate JPA zu verwenden.

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

Nun zuerst, egal was Sie auf einem Hibernate -Proxy -Objekt, einem Setter oder einem Getter anrufen, wird ein SQL abgefeuert und das Objekt geladen.

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

Jetzt können wir alle argumentieren, dass die Auswahl der Primärschlüssel so schnell wie eine Abfrage ist und es nicht wirklich etwas ist, das man zu vermeiden habe. Aber für diejenigen von uns, die es aus dem einen oder anderen Grund nicht bewältigen können, finden Sie unten eine Implementierung eines solchen Proxy. Aber bevor Sie die Implementierung sehen, sehen Sie, dass es sich um die Verwendung handelt und wie einfach es zu verwenden ist.

VERWENDUNGSZWECK

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

Und das würde die folgende Frage abfeuern -

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

Und selbst wenn Sie einfügen möchten, können Sie dennoch Persistenceservice.save (Neue Ordnung ("a", 2) machen); und es würde einen Einsatz so abfeuern, wie es sollte.

IMPLEMENTIERUNG

Fügen Sie dies zu Ihrem pom.xml - hinzu -

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

Machen Sie diese Klasse, um 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());
    }
}

Machen Sie eine Schnittstelle mit allen Methoden -

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

Machen 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 sparen kann -

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

}
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top