So konvertieren Sie einen Hibernate-Proxy in ein reales Entitätsobjekt

160

Während eines Ruhezustands lade Sessionich einige Objekte und einige von ihnen werden aufgrund des verzögerten Ladens als Proxys geladen. Es ist alles in Ordnung und ich möchte das faule Laden nicht ausschalten.

Aber später muss ich einige der Objekte (eigentlich ein Objekt) per RPC an den GWT-Client senden. Und es kommt vor, dass dieses konkrete Objekt ein Proxy ist. Also muss ich daraus ein echtes Objekt machen. Ich kann im Ruhezustand keine Methode wie "materialisieren" finden.

Wie kann ich einige der Objekte von Proxys zu Reals machen, die ihre Klasse und ID kennen?

Im Moment besteht die einzige Lösung darin, dieses Objekt aus dem Cache von Hibernate zu entfernen und neu zu laden, aber es ist aus vielen Gründen wirklich schlecht.

Andrey Minogin
quelle

Antworten:

232

Hier ist eine Methode, die ich verwende.

public static <T> T initializeAndUnproxy(T entity) {
    if (entity == null) {
        throw new 
           NullPointerException("Entity passed for initialization is null");
    }

    Hibernate.initialize(entity);
    if (entity instanceof HibernateProxy) {
        entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer()
                .getImplementation();
    }
    return entity;
}
Bozho
quelle
1
Ich wollte dasselbe tun, also schrieb ich die Proxy-Instanz in einen ObjectOutputStream und las sie dann von einem entsprechenden ObjectInputStream zurück, und das schien den Trick zu tun. Ich bin mir nicht sicher, ob es ein effizienter Ansatz ist, frage mich aber immer noch, warum es funktioniert hat ... Kommentare dazu werden sehr geschätzt. Vielen Dank!
shrini1000
@ shrini1000 es hat funktioniert, weil beim Serialisieren die Sammlung initialisiert wird (wenn die Sitzung noch nicht geschlossen ist). Auch HibernateProxydefiniert eine writeReplaceMethode , um Implementierer zu zwingen etwas Besonderes während der Serialisierung zu tun.
Bozho
1
Gibt es eine tragbare (JPA) Möglichkeit, dies zu tun?
Kawu
Warum löst Hibernate.initialize lazyInitializeException aus, wenn ich es aufrufe? Ich benutze nur wie: Object o = session.get (MyClass.class, id); Objekt other = o.getSomeOtherClass (); initializeAndUnproxy (other);
Fredcrs
6
Sie können das gleiche ohne Ihre eigene Util-Klasse tun -(T)Hibernate.unproxy(entity)
Panser
46

Wie ich in diesem Artikel erklärt habe , können Sie dies seit Hibernate ORM 5.2.10 folgendermaßen tun:

Object unproxiedEntity = Hibernate.unproxy(proxy);

Vor dem Ruhezustand 5.2.10 . Der einfachste Weg, dies zu tun, war die Verwendung der Unproxy- Methode, die von der internen PersistenceContextImplementierung von Hibernate angeboten wird :

Object unproxiedEntity = ((SessionImplementor) session)
                         .getPersistenceContext()
                         .unproxy(proxy);
Vlad Mihalcea
quelle
Behandelt der Aufruf einer übergeordneten Entität Sammlungsfelder? zB, wenn Sie eine Departmentmit Liste von haben Student, müssen Sie noch unproxy(department.getStudents()) - oder ist es genug, um nur unproxy(department)?
trafalmadorian
1
Nur der angegebene Proxy wird initialisiert. Es kaskadiert nicht zu Zuordnungen, da dies möglicherweise Tonnen von Daten laden könnte, wenn Sie die Proxy-Funktion einer Stammentität aufheben.
Vlad Mihalcea
Allerdings PersistentContext#unproxy(proxy)wirft eine Ausnahme , wenn der Proxy nicht initialisiert , während Hibernate.unproxy(proxy)und LazyInitializer#getImplementation(proxy)initialisieren den Proxy , falls erforderlich. Habe gerade eine Ausnahme wegen dieses Unterschieds gefangen. ;-)
bgraves
13

Ich habe folgenden Code geschrieben, der das Objekt von Proxys bereinigt (falls diese noch nicht initialisiert sind).

public class PersistenceUtils {

    private static void cleanFromProxies(Object value, List<Object> handledObjects) {
        if ((value != null) && (!isProxy(value)) && !containsTotallyEqual(handledObjects, value)) {
            handledObjects.add(value);
            if (value instanceof Iterable) {
                for (Object item : (Iterable<?>) value) {
                    cleanFromProxies(item, handledObjects);
                }
            } else if (value.getClass().isArray()) {
                for (Object item : (Object[]) value) {
                    cleanFromProxies(item, handledObjects);
                }
            }
            BeanInfo beanInfo = null;
            try {
                beanInfo = Introspector.getBeanInfo(value.getClass());
            } catch (IntrospectionException e) {
                // LOGGER.warn(e.getMessage(), e);
            }
            if (beanInfo != null) {
                for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) {
                    try {
                        if ((property.getWriteMethod() != null) && (property.getReadMethod() != null)) {
                            Object fieldValue = property.getReadMethod().invoke(value);
                            if (isProxy(fieldValue)) {
                                fieldValue = unproxyObject(fieldValue);
                                property.getWriteMethod().invoke(value, fieldValue);
                            }
                            cleanFromProxies(fieldValue, handledObjects);
                        }
                    } catch (Exception e) {
                        // LOGGER.warn(e.getMessage(), e);
                    }
                }
            }
        }
    }

    public static <T> T cleanFromProxies(T value) {
        T result = unproxyObject(value);
        cleanFromProxies(result, new ArrayList<Object>());
        return result;
    }

    private static boolean containsTotallyEqual(Collection<?> collection, Object value) {
        if (CollectionUtils.isEmpty(collection)) {
            return false;
        }
        for (Object object : collection) {
            if (object == value) {
                return true;
            }
        }
        return false;
    }

    public static boolean isProxy(Object value) {
        if (value == null) {
            return false;
        }
        if ((value instanceof HibernateProxy) || (value instanceof PersistentCollection)) {
            return true;
        }
        return false;
    }

    private static Object unproxyHibernateProxy(HibernateProxy hibernateProxy) {
        Object result = hibernateProxy.writeReplace();
        if (!(result instanceof SerializableProxy)) {
            return result;
        }
        return null;
    }

    @SuppressWarnings("unchecked")
    private static <T> T unproxyObject(T object) {
        if (isProxy(object)) {
            if (object instanceof PersistentCollection) {
                PersistentCollection persistentCollection = (PersistentCollection) object;
                return (T) unproxyPersistentCollection(persistentCollection);
            } else if (object instanceof HibernateProxy) {
                HibernateProxy hibernateProxy = (HibernateProxy) object;
                return (T) unproxyHibernateProxy(hibernateProxy);
            } else {
                return null;
            }
        }
        return object;
    }

    private static Object unproxyPersistentCollection(PersistentCollection persistentCollection) {
        if (persistentCollection instanceof PersistentSet) {
            return unproxyPersistentSet((Map<?, ?>) persistentCollection.getStoredSnapshot());
        }
        return persistentCollection.getStoredSnapshot();
    }

    private static <T> Set<T> unproxyPersistentSet(Map<T, ?> persistenceSet) {
        return new LinkedHashSet<T>(persistenceSet.keySet());
    }

}

Ich verwende diese Funktion über dem Ergebnis meiner RPC-Dienste (über Aspekte) und bereinigt rekursiv alle Ergebnisobjekte von Proxys (wenn sie nicht initialisiert sind).

Sergey Bondarev
quelle
Vielen Dank für das Teilen dieses Codes, obwohl er nicht alle Anwendungsfälle abgedeckt hat, aber wirklich hilfreich ist ...
Prateek Singh
Richtig. Es sollte entsprechend neuen Fällen aktualisiert werden. Sie könnten Dinge ausprobieren, die von GWT-Leuten empfohlen werden. Schauen Sie hier: gwtproject.org/articles/using_gwt_with_hibernate.html (siehe Teil Integrationsstrategien). Im Allgemeinen empfehlen sie die Verwendung von DTO oder Dozer oder Gilead. Es wird in Ordnung sein, wenn Sie Ihre Meinung dazu abgeben. In meinem Fall sieht es so aus, als wäre mein Code die einfachste Lösung, aber nicht voll = (.
Sergey Bondarev
Vielen Dank. Wo können wir eine Implementierung für "CollectionsUtils.containsTotallyEqual (handleObjects, value)" erhalten?
Ilan.K
public static boolean enthältTotallyEqual (Sammlung <?> Sammlung, Objektwert) {if (isEmpty (Sammlung)) {return false; } for (Objekt Objekt: Sammlung) {if (Objekt == Wert) {return true; } } falsch zurückgeben; }
Sergey Bondarev
Es ist nur eine von mir selbst erstellte Dienstprogrammmethode
Sergey Bondarev,
12

Versuchen zu benutzen Hibernate.getClass(obj)

Sanek Shu
quelle
15
Dies gibt die Klasse zurück und nicht das deproxierte Objekt selbst
Stefan Haberl
Tatsächlich ist diese Lösung großartig, wenn wir versuchen, die Klasse von Objekten zum Beispiel für Vergleiche zu finden.
João Rebelo
10

So empfehle ich JPA 2:

Object unproxied  = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().unproxy(proxy);
Yannis JULIENNE
quelle
2
Wie unterscheidet sich Ihre Antwort von meiner?
Vlad Mihalcea
Ich habe diese Lösung ausprobiert ... funktioniert nicht immer, wenn Sie so etwas nicht vor den Unwrap-Befehl setzen: HibernateProxy hibernateProxy = (HibernateProxy) MöglichProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initialize (); }}
user3227576
2

Mit Spring Data JPA und Hibernate habe ich Subschnittstellen von verwendet JpaRepository, um Objekte zu suchen, die zu einer Typhierarchie gehören, die mithilfe der "Join" -Strategie zugeordnet wurde. Leider gaben die Abfragen Proxys des Basistyps anstelle von Instanzen der erwarteten konkreten Typen zurück. Dies hinderte mich daran, die Ergebnisse auf die richtigen Typen zu übertragen. Wie Sie bin ich hierher gekommen, um einen effektiven Weg zu finden, um meine Entitäten unberührt zu lassen.

Vlad hat die richtige Idee, diese Ergebnisse zu entkräften. Yannis liefert etwas mehr Details. Im Anschluss an ihre Antworten finden Sie hier den Rest von dem, wonach Sie suchen könnten:

Der folgende Code bietet eine einfache Möglichkeit, die Proxy-Funktionen Ihrer Proxy-Entitäten aufzuheben:

import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionImplementor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaContext;
import org.springframework.stereotype.Component;

@Component
public final class JpaHibernateUtil {

    private static JpaContext jpaContext;

    @Autowired
    JpaHibernateUtil(JpaContext jpaContext) {
        JpaHibernateUtil.jpaContext = jpaContext;
    }

    public static <Type> Type unproxy(Type proxied, Class<Type> type) {
        PersistenceContext persistenceContext =
            jpaContext
            .getEntityManagerByManagedType(type)
            .unwrap(SessionImplementor.class)
            .getPersistenceContext();
        Type unproxied = (Type) persistenceContext.unproxyAndReassociate(proxied);
        return unproxied;
    }

}

Sie können der unproxyMethode entweder nicht proxisierte Entitäten oder Proxy-Entitäten übergeben . Wenn sie bereits nicht ordnungsgemäß sind, werden sie einfach zurückgegeben. Andernfalls werden sie nicht entlastet und zurückgegeben.

Hoffe das hilft!

Sharky
quelle
1

Die andere Problemumgehung besteht darin, anzurufen

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Kurz vor dem Schließen der Sitzung.

0x6B6F77616C74
quelle
1

Ich habe eine Lösung gefunden, um eine Klasse mithilfe der Standard-Java- und JPA-API zu deproxy. Getestet mit Ruhezustand, erfordert jedoch keinen Ruhezustand als Abhängigkeit und sollte mit allen JPA-Anbietern funktionieren.

Eine einzige Anforderung - es ist erforderlich, die übergeordnete Klasse (Adresse) zu ändern und eine einfache Hilfsmethode hinzuzufügen.

Allgemeine Idee: Fügen Sie der übergeordneten Klasse eine Hilfsmethode hinzu, die sich selbst zurückgibt. Wenn die Methode auf dem Proxy aufgerufen wird, leitet sie den Aufruf an die reale Instanz weiter und gibt diese reale Instanz zurück.

Die Implementierung ist etwas komplexer, da der Ruhezustand erkennt, dass die Proxy-Klasse sich selbst zurückgibt und weiterhin den Proxy anstelle der realen Instanz zurückgibt. Umgehung besteht darin, die zurückgegebene Instanz in eine einfache Wrapper-Klasse zu verpacken, die einen anderen Klassentyp als die reale Instanz hat.

In Code:

class Address {
   public AddressWrapper getWrappedSelf() {
       return new AddressWrapper(this);
   }
...
}

class AddressWrapper {
    private Address wrappedAddress;
...
}

Verwenden Sie Folgendes, um den Adressproxy in eine echte Unterklasse umzuwandeln:

Address address = dao.getSomeAddress(...);
Address deproxiedAddress = address.getWrappedSelf().getWrappedAddress();
if (deproxiedAddress instanceof WorkAddress) {
WorkAddress workAddress = (WorkAddress)deproxiedAddress;
}
OndroMih
quelle
Ihr Beispielcode scheint etwas unklar zu sein (oder ich brauche einfach mehr Kaffee). Woher kommt EntityWrapper? sollte das AddressWrapper sein? Und ich vermute, AddressWrapped sollte AddressWrapper sagen? Können Sie das klarstellen?
Gus
@ Gus, du hast recht. Ich habe das Beispiel korrigiert. Danke :)
OndroMih
0

Vielen Dank für die vorgeschlagenen Lösungen! Leider hat keiner von ihnen für meinen Fall funktioniert: Empfangen einer Liste von CLOB-Objekten aus der Oracle-Datenbank über JPA - Hibernate mithilfe einer nativen Abfrage.

Alle vorgeschlagenen Ansätze gaben mir entweder eine ClassCastException oder gaben nur ein Java-Proxy-Objekt zurück (das tief im Inneren den gewünschten Clob enthielt).

Meine Lösung lautet also wie folgt (basierend auf mehreren oben genannten Ansätzen):

Query sqlQuery = manager.createNativeQuery(queryStr);
List resultList = sqlQuery.getResultList();
for ( Object resultProxy : resultList ) {
    String unproxiedClob = unproxyClob(resultProxy);
    if ( unproxiedClob != null ) {
       resultCollection.add(unproxiedClob);
    }
}

private String unproxyClob(Object proxy) {
    try {
        BeanInfo beanInfo = Introspector.getBeanInfo(proxy.getClass());
        for (PropertyDescriptor property : beanInfo.getPropertyDescriptors()) {
            Method readMethod = property.getReadMethod();
            if ( readMethod.getName().contains("getWrappedClob") ) {
                Object result = readMethod.invoke(proxy);
                return clobToString((Clob) result);
            }
        }
    }
    catch (InvocationTargetException | IntrospectionException | IllegalAccessException | SQLException | IOException e) {
        LOG.error("Unable to unproxy CLOB value.", e);
    }
    return null;
}

private String clobToString(Clob data) throws SQLException, IOException {
    StringBuilder sb = new StringBuilder();
    Reader reader = data.getCharacterStream();
    BufferedReader br = new BufferedReader(reader);

    String line;
    while( null != (line = br.readLine()) ) {
        sb.append(line);
    }
    br.close();

    return sb.toString();
}

Hoffe das wird jemandem helfen!

Dmitry
quelle