Come convertire un proxy Hibernate in un oggetto entità reale


161

Durante un letargo Session, sto caricando alcuni oggetti e alcuni di essi vengono caricati come proxy a causa del caricamento lento. Va tutto bene e non voglio disattivare il caricamento lento.

Ma in seguito ho bisogno di inviare alcuni degli oggetti (in realtà un oggetto) al client GWT tramite RPC. E succede che questo oggetto concreto è un proxy. Quindi ho bisogno di trasformarlo in un oggetto reale. Non riesco a trovare un metodo come "materializzare" in Hibernate.

Come posso trasformare alcuni degli oggetti da proxy a reali conoscendo la loro classe e ID?

Al momento l'unica soluzione che vedo è quella di sfrattare quell'oggetto dalla cache di Hibernate e ricaricarlo, ma è davvero male per molte ragioni.

Risposte:


232

Ecco un metodo che sto usando.

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

1
Volevo fare la stessa cosa, quindi ho scritto l'istanza proxy su un ObjectOutputStream e poi l'ho letta da un ObjectInputStream corrispondente, e questo sembrava fare il trucco. Non sono sicuro che si tratti di un approccio efficiente, ma mi chiedo ancora perché abbia funzionato ... eventuali commenti saranno apprezzati molto. Grazie!
shrini1000

@ shrini1000 ha funzionato perché durante la serializzazione inizializza la raccolta (se la sessione non è ancora chiusa). HibernateProxyDefinisce inoltre un writeReplacemetodo per forzare gli implementatori a fare qualcosa di speciale durante la serializzazione.
Bozho,

1
Esiste un modo portatile (JPA) per farlo?
Kawu,

perché, Hibernate.initialize lanciando pigroInitializeException quando lo chiamo? Sto solo usando like: Object o = session.get (MyClass.class, id); Object other = o.getSomeOtherClass (); initializeAndUnproxy (altri);
Fredcrs,

6
puoi fare lo stesso senza la tua classe util -(T)Hibernate.unproxy(entity)
panser

47

Come ho spiegato in questo articolo , da Hibernate ORM 5.2.10 , puoi farlo come questo:

Object unproxiedEntity = Hibernate.unproxy(proxy);

Prima di ibernazione 5.2.10 . il modo più semplice per farlo era utilizzare il metodo non proxy offerto dall'implementazione interna di Hibernate PersistenceContext:

Object unproxiedEntity = ((SessionImplementor) session)
                         .getPersistenceContext()
                         .unproxy(proxy);

La chiamata su un'entità padre gestisce i campi di raccolta ?? ad esempio, se si dispone di un Departmentelenco di Student, è ancora necessario unproxy(department.getStudents()) - o è sufficiente solo unproxy(department)?
trafalmadoriano,

1
Viene inizializzato solo il proxy specificato. Non si collega alle associazioni, in quanto ciò potrebbe potenzialmente caricare tonnellate di dati in caso di non proxy di un'entità radice.
Vlad Mihalcea,

Tuttavia, PersistentContext#unproxy(proxy)genera un'eccezione se il proxy non è inizializzato mentre Hibernate.unproxy(proxy)e LazyInitializer#getImplementation(proxy)inizializza il proxy, se necessario. Ho appena preso un'eccezione a causa di questa differenza. ;-)
bgraves

13

Prova ad usare Hibernate.getClass(obj)


15
Questo restituisce la classe piuttosto che l'oggetto deproxato stesso
Stefan Haberl,

In realtà questa soluzione è eccezionale quando stiamo cercando di trovare la classe di oggetti per esempio di confronti.
João Rebelo,

13

Ho scritto il seguente codice che pulisce l'oggetto dai proxy (se non sono già inizializzati)

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

}

Uso questa funzione sul risultato dei miei servizi RPC (tramite aspetti) e pulisce ricorsivamente tutti gli oggetti risultato dai proxy (se non sono inizializzati).


grazie per aver condiviso questo codice anche se non ha coperto tutti i casi d'uso ma è davvero utile ...
Prateek Singh

Corretta. Dovrebbe essere aggiornato in base ai nuovi casi. Potresti provare le cose consigliate dai ragazzi di GWT. Guarda qui: gwtproject.org/articles/using_gwt_with_hibernate.html (vedi parte Strategie di integrazione). In generale raccomandano di usare DTO o Dozer o Gilead. Andrà bene se fornirai la tua opinione al riguardo. Nel mio caso sembra che il mio codice sia la soluzione più semplice, ma non completa = (.
Sergey Bondarev

Grazie. dove possiamo ottenere un'implementazione per "CollectionsUtils.containsTotallyEqual (handledObjects, value)"?
Ilan.K,

public static boolean contieneTotallyEqual (Collection <?> collection, Object value) {if (isEmpty (collection)) {return false; } for (Object object: collection) {if (object == value) {return true; }} return false; }
Sergey Bondarev,

È solo un metodo di utilità creato da me stesso
Sergey Bondarev,

10

Il modo in cui raccomando con JPA 2:

Object unproxied  = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().unproxy(proxy);

2
In che modo la tua risposta è diversa dalla mia?
Vlad Mihalcea,

Ho provato questa soluzione ... non funziona sempre se non metti qualcosa del genere prima del comando unwrap: HibernateProxy hibernateProxy = (HibernateProxy) possibleProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initialize (); }
user3227576

2

Con Spring Data JPA e Hibernate, stavo usando le interfacce secondarie JpaRepositoryper cercare oggetti appartenenti a una gerarchia di tipi mappata usando la strategia "join". Sfortunatamente, le query restituivano proxy del tipo base anziché istanze dei tipi concreti previsti. Questo mi ha impedito di trasmettere i risultati ai tipi corretti. Come te, sono venuto qui alla ricerca di un modo efficace per rendere i miei enti privi di proxy.

Vlad ha l'idea giusta per non compromettere questi risultati; Yannis fornisce un po 'più di dettagli. Aggiungendo alle loro risposte, ecco il resto di quello che potresti cercare:

Il codice seguente fornisce un modo semplice per annullare il proxy delle entità proxy:

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

}

È possibile passare al unproxymetodo entità non proxy o entità proxy . Se non sono già stati sottoposti a proxy, verranno semplicemente restituiti. Altrimenti, non verranno sottoposti a proxy e verranno restituiti.

Spero che questo ti aiuti!


1

Un'altra soluzione alternativa è chiamare

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Poco prima di chiudere la sessione.


1

Ho trovato una soluzione per deproxy di una classe utilizzando l'API Java e JPA standard. Testato con ibernazione, ma non richiede l'ibernazione come dipendenza e dovrebbe funzionare con tutti i provider JPA.

Un solo requisito: è necessario modificare la classe genitore (indirizzo) e aggiungere un semplice metodo di supporto.

Idea generale: aggiungi il metodo helper alla classe genitore che restituisce se stesso. quando il metodo ha chiamato il proxy, inoltrerà la chiamata all'istanza reale e restituirà questa istanza reale.

L'implementazione è un po 'più complessa, poiché l'ibernazione riconosce che la classe proxy restituisce se stessa e restituisce comunque il proxy anziché l'istanza reale. La soluzione alternativa consiste nell'avvolgere l'istanza restituita in una semplice classe wrapper, che ha un tipo di classe diverso dall'istanza reale.

Nel codice:

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

class AddressWrapper {
    private Address wrappedAddress;
...
}

Per trasmettere il proxy degli indirizzi alla sottoclasse reale, utilizzare quanto segue:

Address address = dao.getSomeAddress(...);
Address deproxiedAddress = address.getWrappedSelf().getWrappedAddress();
if (deproxiedAddress instanceof WorkAddress) {
WorkAddress workAddress = (WorkAddress)deproxiedAddress;
}

Il tuo codice di esempio sembra un po 'poco chiaro (o forse ho solo bisogno di più caffè). Da dove viene EntityWrapper? dovrebbe essere AddressWrapper? E immagino che AddressWrapped dovrebbe dire AddressWrapper? Puoi chiarire questo?
Gus,

@Gus, hai ragione. Ho corretto l'esempio. Grazie :)
OndroMih,

1

A partire da Hiebrnate 5.2.10 è possibile utilizzare il metodo Hibernate.proxy per convertire un proxy nella propria entità reale:

MyEntity myEntity = (MyEntity) Hibernate.unproxy( proxyMyEntity );

0

Grazie per le soluzioni suggerite! Sfortunatamente, nessuno di loro ha funzionato per il mio caso: ricevere un elenco di oggetti CLOB dal database Oracle tramite JPA - Hibernate, usando una query nativa.

Tutti gli approcci proposti mi hanno dato un oggetto ClassCastException o appena restituito un oggetto proxy Java (che conteneva profondamente il Clob desiderato).

Quindi la mia soluzione è la seguente (basata su diversi approcci di cui sopra):

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

Spero che questo possa aiutare qualcuno!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.