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.