Jak przekonwertować proxy Hibernate do rzeczywistego obiektu encji


161

Podczas hibernacji Sessionładuję niektóre obiekty, a niektóre z nich są ładowane jako proxy z powodu leniwego ładowania. Wszystko jest w porządku i nie chcę wyłączać leniwego ładowania.

Ale później muszę wysłać niektóre obiekty (właściwie jeden obiekt) do klienta GWT za pośrednictwem RPC. I zdarza się, że ten konkretny obiekt jest proxy. Muszę więc przekształcić go w prawdziwy obiekt. Nie mogę znaleźć metody takiej jak „materializacja” w Hibernate.

Jak zmienić niektóre obiekty z serwerów proxy w rzeczywiste, znając ich klasę i identyfikator?

W tej chwili jedynym rozwiązaniem, które widzę, jest wyrzucenie tego obiektu z pamięci podręcznej Hibernate i ponowne załadowanie go, ale jest to naprawdę złe z wielu powodów.

Odpowiedzi:


232

Oto metoda, której używam.

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
Chciałem zrobić to samo, więc napisałem instancję proxy do ObjectOutputStream, a następnie odczytałem ją z powrotem z odpowiedniego ObjectInputStream i wydawało się, że to załatwiło sprawę. Nie jestem pewien, czy to skuteczne podejście, ale wciąż zastanawiam się, dlaczego zadziałało ... Wszelkie uwagi na ten temat będą bardzo mile widziane. Dzięki!
shrini1000

@ shrini1000 zadziałało, ponieważ podczas serializacji inicjalizuje kolekcję (jeśli sesja nie jest jeszcze zamknięta). Również HibernateProxyokreśla writeReplacesposób, aby wymusić implementors zrobić coś wyjątkowego podczas serializacji.
Bozho

1
Czy istnieje przenośny (JPA) sposób, aby to zrobić?
Kawu

dlaczego Hibernate.initialize rzuca lazyInitializeException, kiedy to wywołuję? Im po prostu używam: Object o = session.get (MyClass.class, id); Obiekt other = o.getSomeOtherClass (); initializeAndUnproxy (inne);
fredcrs

6
możesz zrobić to samo bez własnej klasy (T)Hibernate.unproxy(entity)
użytkowej

47

Jak wyjaśniłem w tym artykule , od Hibernate ORM 5.2.10 możesz to zrobić tak:

Object unproxiedEntity = Hibernate.unproxy(proxy);

Przed hibernacją 5.2.10 . najprostszym sposobem na to było użycie metody nieproxy oferowanej przez wewnętrzną PersistenceContextimplementację Hibernate :

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

Czy wywołanie tego w jednostce nadrzędnej obsługuje pola kolekcji? np. jeśli masz Departmentz Listą Student, czy nadal musisz unproxy(department.getStudents()) - czy wystarczy unproxy(department)?
trafalmadorski

1
Inicjowany jest tylko dany serwer proxy. Nie przechodzi kaskadowo do skojarzeń, ponieważ może to potencjalnie załadować mnóstwo danych, jeśli zdarzy się cofnąć proxy jednostki głównej.
Vlad Mihalcea

Jednak PersistentContext#unproxy(proxy)zgłasza wyjątek, jeśli proxy nie jest zainicjowane podczas Hibernate.unproxy(proxy)i LazyInitializer#getImplementation(proxy)inicjuje proxy, jeśli to konieczne. Właśnie złapałem wyjątek z powodu tej różnicy. ;-)
bgraves

13

Spróbuj użyć Hibernate.getClass(obj)


15
To zwraca klasę, a nie sam odbezpieczony obiekt
Stefan Haberl

Właściwie to rozwiązanie jest świetne, gdy próbujemy znaleźć klasę obj na przykład porównań.
João Rebelo

13

Napisałem następujący kod, który czyści obiekt z serwerów proxy (jeśli nie są jeszcze zainicjowane)

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

}

Używam tej funkcji na wynikach moich usług RPC (poprzez aspekty) i czyści ona rekurencyjnie wszystkie obiekty wynikowe z serwerów proxy (jeśli nie są zainicjowane).


dziękuję za udostępnienie tego kodu, chociaż nie obejmował on wszystkich przypadków użycia, ale jest naprawdę pomocny ...
Prateek Singh

Poprawny. Powinien być aktualizowany zgodnie z nowymi przypadkami. Możesz spróbować rzeczy polecanych przez facetów z GWT. Zajrzyj tutaj: gwtproject.org/articles/using_gwt_with_hibernate.html (patrz część Strategie integracji). Ogólnie zalecają używanie DTO lub Dozer lub Gilead. Będzie dobrze, jeśli przedstawisz swoją opinię na ten temat. W moim przypadku wygląda na to, że mój kod jest najprostszym rozwiązaniem, ale nie jest pełny = (.
Sergey Bondarev

dzięki. gdzie możemy uzyskać implementację dla „CollectionsUtils.containsTotallyEqual (handledObjects, value)”?
Ilan.K

public static boolean zawieraTotallyEqual (kolekcja <?> kolekcja, wartość obiektu) {if (isEmpty (kolekcja)) {return false; } for (Object object: collection) {if (object == value) {return true; }} return false; }
Sergey Bondarev

To tylko metoda użytkowa stworzona przeze mnie
Sergey Bondarev

10

Sposób, w jaki polecam z JPA 2:

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

2
Czym twoja odpowiedź różni się od mojej?
Vlad Mihalcea,

Wypróbowałem to rozwiązanie ... nie działa zawsze, jeśli nie umieścisz czegoś takiego przed poleceniem rozpakowywania: HibernateProxy hibernateProxy = (HibernateProxy) possibleProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initialize (); }
user3227576

2

W Spring Data JPA i Hibernate używałem podinterfejsów programu JpaRepositorydo wyszukiwania obiektów należących do hierarchii typów, która została zmapowana przy użyciu strategii „łączenia”. Niestety, zapytania zwracały serwery proxy typu podstawowego zamiast wystąpień oczekiwanych typów konkretnych. To uniemożliwiło mi rzutowanie wyników na prawidłowe typy. Tak jak ty, przyjechałem tutaj, szukając skutecznego sposobu na uwolnienie moich podmiotów.

Vlad ma dobry pomysł na odblokowanie tych wyników; Yannis podaje trochę więcej szczegółów. Dodając do ich odpowiedzi, oto reszta tego, czego możesz szukać:

Poniższy kod zapewnia łatwy sposób na usunięcie proxy jednostek 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;
    }

}

Do unproxymetody można przekazać niezarejestrowane jednostki lub jednostki proxy . Jeśli nie są już zabezpieczone, zostaną po prostu zwrócone. W przeciwnym razie zostaną pozbawione proxy i zwrócone.

Mam nadzieję że to pomoże!


1

Innym sposobem obejścia problemu jest zadzwonić

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Tuż przed zamknięciem sesji.


1

Znalazłem rozwiązanie do deproxyingu klasy przy użyciu standardowego API Java i JPA. Testowane z hibernacją, ale nie wymaga hibernacji jako zależności i powinno działać ze wszystkimi dostawcami JPA.

Jedno wymaganie - konieczne jest zmodyfikowanie klasy nadrzędnej (Adres) i dodanie prostej metody pomocniczej.

Ogólna idea: dodaj metodę pomocniczą do klasy nadrzędnej, która zwraca się sama. gdy metoda zostanie wywołana na serwerze proxy, przekieruje wywołanie do rzeczywistej instancji i zwróci tę rzeczywistą instancję.

Implementacja jest nieco bardziej złożona, ponieważ hibernacja rozpoznaje, że klasa proxy zwraca samą siebie i nadal zwraca serwer proxy zamiast rzeczywistej instancji. Obejście polega na umieszczeniu zwracanej instancji w prostej klasie opakowania, która ma inny typ klasy niż rzeczywista instancja.

W kodzie:

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

class AddressWrapper {
    private Address wrappedAddress;
...
}

Aby przesłać adres proxy na rzeczywistą podklasę, użyj następujących poleceń:

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

Twój przykładowy kod wydaje się trochę niejasny (a może po prostu potrzebuję więcej kawy). Skąd pochodzi EntityWrapper? czy to powinien być AddressWrapper? I zgaduję, że AddressWrapped powinien powiedzieć AddressWrapper? Czy możesz to wyjaśnić?
Gus,

@Gus, masz rację. Poprawiłem przykład. Dzięki :)
OndroMih


0

Dziękuję za zaproponowane rozwiązania! Niestety żaden z nich nie zadziałał w moim przypadku: odebranie listy obiektów CLOB z bazy danych Oracle przez JPA - Hibernate, przy użyciu natywnego zapytania.

Wszystkie proponowane podejścia dały mi albo ClassCastException, albo po prostu zwróciły obiekt Java Proxy (który głęboko w środku zawierał pożądany Clob).

Więc moje rozwiązanie jest następujące (oparte na kilku powyższych podejściach):

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

Mam nadzieję, że to komuś pomoże!

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.