Как преобразовать прокси 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;
}
Bozho
источник
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. Если они уже без прокси, они просто будут возвращены. В противном случае, они будут освобождены от прокси и возвращены.

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

Sharky
источник
1

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

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

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

0x6B6F77616C74
источник
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;
}
OndroMih
источник
Ваш пример кода кажется немного неясным (или, может быть, мне просто нужно больше кофе). Откуда приходит EntityWrapper? это должно быть AddressWrapper? И я предполагаю, что AddressWrapped должен сказать AddressWrapper? Вы можете уточнить это?
Гас
@ Гас, ты прав. Я исправил пример. Спасибо :)
OndroMih
1

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

MyEntity myEntity = (MyEntity) Hibernate.unproxy( proxyMyEntity );
O.Badr
источник
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();
}

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

Дмитрий
источник