Comment convertir un proxy Hibernate en un véritable objet entité

161

Pendant une mise en veille prolongée Session, je charge certains objets et certains d'entre eux sont chargés en tant que proxys en raison d'un chargement paresseux. Tout va bien et je ne veux pas désactiver le chargement paresseux.

Mais plus tard, je dois envoyer certains des objets (en fait un objet) au client GWT via RPC. Et il arrive que cet objet concret soit un proxy. J'ai donc besoin de le transformer en un véritable objet. Je ne trouve pas de méthode comme "matérialiser" dans Hibernate.

Comment puis-je transformer certains objets des proxys en réels en connaissant leur classe et leur ID?

Pour le moment, la seule solution que je vois est d'expulser cet objet du cache d'Hibernate et de le recharger, mais c'est vraiment mauvais pour de nombreuses raisons.

Andrey Minogin
la source

Réponses:

232

Voici une méthode que j'utilise.

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
la source
1
Je voulais faire la même chose, j'ai donc écrit l'instance mandatée dans un ObjectOutputStream, puis l'ai relue à partir d'un ObjectInputStream correspondant, et cela semblait faire l'affaire. Je ne sais pas si c'est une approche efficace, mais je me demande toujours pourquoi cela a fonctionné ... tout commentaire à ce sujet sera grandement apprécié. Merci!
shrini1000
@ shrini1000 cela a fonctionné car lors de la sérialisation initialise la collection (si la session n'est pas encore fermée). HibernateProxyDéfinit également une writeReplaceméthode pour forcer les implémenteurs à faire quelque chose de spécial pendant la sérialisation.
Bozho
1
Existe-t-il un moyen portable (JPA) de faire cela?
Kawu
pourquoi, Hibernate.initialize lançant lazyInitializeException lorsque je l'appelle? Im utilisant juste comme: Object o = session.get (MyClass.class, id); Object autre = o.getSomeOtherClass (); initializeAndUnproxy (autre);
fredcrs
6
vous pouvez faire la même chose sans votre propre classe util -(T)Hibernate.unproxy(entity)
panser
47

Comme je l'ai expliqué dans cet article , depuis Hibernate ORM 5.2.10 , vous pouvez le faire comme ceci:

Object unproxiedEntity = Hibernate.unproxy(proxy);

Avant Hibernate 5.2.10 . le moyen le plus simple de le faire était d'utiliser la méthode non proxy offerte par l' PersistenceContextimplémentation interne d'Hibernate :

Object unproxiedEntity = ((SessionImplementor) session)
                         .getPersistenceContext()
                         .unproxy(proxy);
Vlad Mihalcea
la source
L'appel de ceci sur une entité parente gère-t-il les champs de collection? Par exemple, si vous avez une Departmentliste avec Student, avez-vous toujours besoin de unproxy(department.getStudents()) - ou est-ce suffisant unproxy(department)?
trafalmadorian
1
Seul le proxy donné est initialisé. Il ne se répercute pas sur les associations, car cela pourrait potentiellement charger des tonnes de données si vous annulez le proxy d'une entité racine.
Vlad Mihalcea
Cependant PersistentContext#unproxy(proxy)lève une exception si le proxy n'est pas initialisé pendant Hibernate.unproxy(proxy)et LazyInitializer#getImplementation(proxy)initialise le proxy si nécessaire. Juste attrapé une exception en raison de cette différence. ;-)
bgraves
13

Essayez d'utiliser Hibernate.getClass(obj)

Sanek Shu
la source
15
Cela renvoie la classe plutôt que l'objet désintégré lui
Stefan Haberl
En fait, cette solution est excellente lorsque nous essayons de trouver la classe d'obj par exemple de comparaisons.
João Rebelo
13

J'ai écrit le code suivant qui nettoie l'objet des proxies (s'ils ne sont pas déjà initialisés)

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

}

J'utilise cette fonction sur le résultat de mes services RPC (via les aspects) et elle nettoie récursivement tous les objets de résultat des proxys (s'ils ne sont pas initialisés).

Sergey Bondarev
la source
merci d'avoir partagé ce code bien qu'il n'ait pas couvert tous les cas d'utilisation, mais il est vraiment utile ...
Prateek Singh
Correct. Il devrait être mis à jour en fonction des nouveaux cas. Vous pouvez essayer les choses recommandées par les gars de GWT. Regardez ici: gwtproject.org/articles/using_gwt_with_hibernate.html (voir la partie Stratégies d'intégration). En général, ils recommandent d'utiliser DTO ou Dozer ou Gilead. Ce sera bien si vous donnez votre avis à ce sujet. Dans mon cas, il semble que mon code est la solution la plus simple, mais pas complète = (.
Sergey Bondarev
Merci. où pouvons-nous obtenir une implémentation pour "CollectionsUtils.containsTotallyEqual (managedObjects, value)"?
Ilan.K
public static boolean containsTotallyEqual (Collection <?> collection, Object value) {if (isEmpty (collection)) {return false; } for (Object object: collection) {if (object == value) {return true; }} return false; }
Sergey Bondarev
C'est juste une méthode utilitaire créée par moi
Sergey Bondarev
10

La façon dont je recommande avec JPA 2:

Object unproxied  = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().unproxy(proxy);
Yannis JULIENNE
la source
2
En quoi votre réponse est-elle différente de la mienne?
Vlad Mihalcea le
J'ai essayé cette solution ... ne fonctionne pas toujours si vous ne mettez pas quelque chose comme ça avant la commande unwrap: HibernateProxy hibernateProxy = (HibernateProxy) possibleProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initialize (); }
user3227576
2

Avec Spring Data JPA et Hibernate, j'utilisais des sous-interfaces de JpaRepositorypour rechercher des objets appartenant à une hiérarchie de types mappée à l'aide de la stratégie de «jointure». Malheureusement, les requêtes renvoyaient des proxys du type de base au lieu d'instances des types concrets attendus. Cela m'a empêché de convertir les résultats dans les types appropriés. Comme vous, je suis venu ici à la recherche d'un moyen efficace de me débarrasser de mes émotions.

Vlad a la bonne idée pour désintoxiquer ces résultats; Yannis fournit un peu plus de détails. En plus de leurs réponses, voici le reste de ce que vous recherchez peut-être:

Le code suivant fournit un moyen simple de supprimer le proxy de vos entités mandatées:

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

}

Vous pouvez transmettre des entités non oxydées ou des entités proxy à la unproxyméthode. S'ils sont déjà non corrigés, ils seront simplement renvoyés. Sinon, ils seront sans oxy et retournés.

J'espère que cela t'aides!

Sharky
la source
1

L'autre solution de contournement consiste à appeler

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Juste avant de fermer la session.

0x6B6F77616C74
la source
1

J'ai trouvé une solution pour déproxy une classe en utilisant les API Java et JPA standard. Testé avec hibernate, mais ne nécessite pas d'hibernation en tant que dépendance et devrait fonctionner avec tous les fournisseurs JPA.

Une seule exigence - il est nécessaire de modifier la classe parent (Address) et d'ajouter une méthode d'assistance simple.

Idée générale: ajouter une méthode d'assistance à la classe parent qui se retourne. lorsque la méthode est appelée sur le proxy, elle transfère l'appel vers une instance réelle et renvoie cette instance réelle.

La mise en œuvre est un peu plus complexe, car hibernate reconnaît que la classe mandatée se renvoie elle-même et renvoie toujours le proxy au lieu de l'instance réelle. La solution de contournement consiste à envelopper l'instance retournée dans une classe wrapper simple, qui a un type de classe différent de celui de l'instance réelle.

Dans du code:

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

class AddressWrapper {
    private Address wrappedAddress;
...
}

Pour convertir le proxy d'adresse en sous-classe réelle, utilisez ce qui suit:

Address address = dao.getSomeAddress(...);
Address deproxiedAddress = address.getWrappedSelf().getWrappedAddress();
if (deproxiedAddress instanceof WorkAddress) {
WorkAddress workAddress = (WorkAddress)deproxiedAddress;
}
OndroMih
la source
Votre exemple de code semble un peu flou (ou peut-être ai-je juste besoin de plus de café). D'où vient EntityWrapper? devrait-ce être AddressWrapper? Et je suppose que AddressWrapped devrait dire AddressWrapper? Pouvez-vous clarifier cela?
Gus
@Gus, vous avez raison. J'ai corrigé l'exemple. Merci :)
OndroMih
1

À partir de Hiebrnate 5.2.10, vous pouvez utiliser la méthode Hibernate.proxy pour convertir un proxy en votre entité réelle:

MyEntity myEntity = (MyEntity) Hibernate.unproxy( proxyMyEntity );
O.Badr
la source
0

Merci pour les solutions proposées! Malheureusement, aucun d'entre eux n'a fonctionné pour mon cas: recevoir une liste d'objets CLOB de la base de données Oracle via JPA - Hibernate, en utilisant une requête native.

Toutes les approches proposées m'ont donné soit une ClassCastException, soit juste un objet proxy java renvoyé (qui contenait profondément à l'intérieur le Clob souhaité).

Ma solution est donc la suivante (basée sur plusieurs approches ci-dessus):

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

J'espère que cela aidera quelqu'un!

Dmitry
la source