Как преобразовать прокси Hibernate в реальный объект


161

Во время Hibernate Sessionя загружаю некоторые объекты, и некоторые из них загружаются как прокси из-за отложенной загрузки. Все в порядке, и я не хочу отключать ленивую загрузку.

Но позже мне нужно отправить некоторые объекты (фактически один объект) клиенту GWT через RPC. И бывает, что этот конкретный объект является прокси. Поэтому мне нужно превратить его в настоящий объект. Я не могу найти метод, как "материализовать" в Hibernate.

Как я могу превратить некоторые объекты из прокси в действительные, зная их класс и ID?

На данный момент единственное решение, которое я вижу, - это извлечь этот объект из кэша Hibernate и перезагрузить его, но это действительно плохо по многим причинам.

Ответы:


232

Вот метод, который я использую.

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
Я хотел сделать то же самое, поэтому я записал проксированный экземпляр в ObjectOutputStream, а затем прочитал его обратно из соответствующего ObjectInputStream, и это, похоже, помогло. Я не уверен, что это эффективный подход, но все еще задаюсь вопросом, почему это сработало ... любые комментарии по этому поводу будут с благодарностью. Спасибо!
— shrini1000

@ shrini1000 работало потому, что при сериализации инициализирует коллекцию (если сессия еще не закрыта). Также HibernateProxyопределяет writeReplaceметод, чтобы заставить разработчиков делать что-то особенное во время сериализации.
— Божо

1
Есть ли портативный (JPA) способ сделать это?
— Каву

почему Hibernate.initialize выбрасывает lazyInitializeException, когда я его вызываю? Я просто использую как: Object o = session.get (MyClass.class, id); Object other = o.getSomeOtherClass (); initializeAndUnproxy (другой);
— fredcrs

6
вы можете сделать то же самое без вашего собственного класса (T)Hibernate.unproxy(entity)
— утилит

47

Как я объяснил в этой статье , начиная с Hibernate ORM 5.2.10 , вы можете сделать это следующим образом:

Object unproxiedEntity = Hibernate.unproxy(proxy);

До гибернации 5.2.10 . Самый простой способ сделать это - использовать метод unproxy, предлагаемый внутренней PersistenceContextреализацией Hibernate :

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

Обрабатывает ли это вызов родительской сущности для обработки полей коллекции? Например, если у вас есть Departmentсо списком Student, вам все еще нужно unproxy(department.getStudents()) - или достаточно просто unproxy(department)?
— Трафальмадорианец

1
Только данный прокси инициализируется. Он не распространяется на ассоциации, так как это может потенциально загрузить тонны данных, если вам случится отменить прокси корневого объекта.
— Влад Михальча

Однако PersistentContext#unproxy(proxy)выдает исключение, если прокси не инициализирован во время Hibernate.unproxy(proxy)и LazyInitializer#getImplementation(proxy)инициализирует прокси при необходимости. Просто поймали исключение из-за этой разницы. ;-)
— bgraves

13

Попробуй использовать Hibernate.getClass(obj)


15
Это возвращает класс, а не сам по себе депроксированный объект
— Стефан Хаберл

На самом деле это решение замечательно, когда мы пытаемся найти класс obj для сравнения.
— Жоао Ребело

13

Я написал следующий код, который очищает объект от прокси (если они еще не инициализированы)

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

}

Я использую эту функцию поверх результатов моих служб RPC (через аспекты), и она рекурсивно очищает все объекты результатов от прокси (если они не инициализированы).


спасибо, что поделились этим кодом, хотя он не охватил все случаи использования, но он действительно полезен ...
— Prateek Singh

Верный. Это должно быть обновлено в соответствии с новыми случаями. Вы можете попробовать вещи, рекомендованные ребятами GWT. Смотрите здесь: gwtproject.org/articles/using_gwt_with_hibernate.html (см. Часть «Стратегии интеграции»). В общем, они рекомендуют использовать DTO или Dozer или Gilead. Будет хорошо, если вы выскажете свое мнение по этому поводу. В моем случае это выглядит, мой код является самым простым решением, но не полным = (.
— Сергей Бондарев

Спасибо. где мы можем получить реализацию для "CollectionsUtils.containsTotallyEqual (handledObjects, value)"?
— Ilan.K

public static boolean containsTotallyEqual (Collection <?> collection, Object value) {if (isEmpty (collection)) {return false; } for (Объект object: collection) {if (object == value) {return true; }} return false; }
— Сергей Бондарев

Это просто служебный метод, созданный мной
— Сергей Бондарев

10

Способ, который я рекомендую с JPA 2:

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

2
Чем ваш ответ отличается от моего?
— Влад Михалча

Я пробовал это решение ... не всегда работает, если вы не помещаете что-то подобное перед командой unwrap: HibernateProxy hibernateProxy = (HibernateProxy) возможный ProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initialize (); }
— user3227576

2

В Spring Data JPA и Hibernate я использовал подынтерфейсы JpaRepositoryдля поиска объектов, принадлежащих иерархии типов, которая была отображена с использованием стратегии «соединения». К сожалению, запросы возвращали прокси базового типа, а не экземпляры ожидаемых конкретных типов. Это помешало мне привести результаты к правильным типам. Как и вы, я приехал сюда в поисках эффективного способа избавить мои энтузиасты.

У Влада есть правильная идея снять эти результаты; Яннис дает немного больше деталей. В дополнение к их ответам, вот что вы можете искать:

Следующий код обеспечивает простой способ отсоединения прокси-объектов.

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

}

Вы можете передавать в метод незаксируемые объекты или объекты с прокси unproxy. Если они уже без прокси, они просто будут возвращены. В противном случае, они будут освобождены от прокси и возвращены.

Надеюсь это поможет!


1

Другой обходной путь должен позвонить

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Прямо перед закрытием сессии.


1

Я нашел решение отменить класс, используя стандартные API Java и JPA. Протестировано с hibernate, но не требует hibernate в качестве зависимости и должно работать со всеми поставщиками JPA.

Единственное требование - необходимо изменить родительский класс (Address) и добавить простой вспомогательный метод.

Общая идея: добавить вспомогательный метод в родительский класс, который возвращает сам себя. когда метод вызывается через прокси, он переадресует вызов реальному экземпляру и возвращает этот реальный экземпляр.

Реализация немного сложнее, поскольку hibernate распознает, что прокси-класс возвращает себя и все еще возвращает прокси вместо реального экземпляра. Обходной путь - обернуть возвращаемый экземпляр в простой класс-оболочку, тип класса которого отличается от реального экземпляра.

В коде:

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

class AddressWrapper {
    private Address wrappedAddress;
...
}

Чтобы привести Address proxy к реальному подклассу, используйте следующее:

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

Ваш пример кода кажется немного неясным (или, может быть, мне просто нужно больше кофе). Откуда приходит EntityWrapper? это должно быть AddressWrapper? И я предполагаю, что AddressWrapped должен сказать AddressWrapper? Вы можете уточнить это?
— Гас

@ Гас, ты прав. Я исправил пример. Спасибо :)
— OndroMih

1

Начиная с Hiebrnate 5.2.10 вы можете использовать метод Hibernate.proxy для преобразования прокси в вашу реальную сущность:

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

0

Спасибо за предложенные решения! К сожалению, ни один из них не сработал для моего случая: получение списка объектов CLOB из базы данных Oracle через JPA - Hibernate с использованием собственного запроса.

Все предложенные подходы дали мне либо ClassCastException, либо просто вернули java Proxy-объект (который глубоко внутри содержал желаемый Clob).

Таким образом, мое решение заключается в следующем (на основе нескольких вышеуказанных подходов):

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

Надеюсь, это кому-нибудь поможет!

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.