如何将Hibernate代理转换为真实的实体对象

191

在 Hibernate 的 Session 中,我正在加载一些对象,并且由于延迟加载,其中一些对象被加载为代理。这完全没问题,我不想关闭延迟加载。

但是后来我需要通过 RPC 将其中一些对象(实际上是一个对象)发送给 GWT 客户端。而恰好这个具体的对象是一个代理。因此我需要将它转换成真正的对象。我找不到像 Hibernate 中的"materialize"这样的方法。

如何知道对象的类和 ID 将某些对象从代理转换为真实对象呢?

目前我唯一能想到的解决方法是将该对象从 Hibernate 的缓存中移除并重新加载它,但由于许多原因,这真的很糟糕。

10个回答

259
这是我正在使用的一种方法。
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
1
有没有一种便携式(JPA)的方法来做到这一点? - Kawu
但是调用 Hibernate.initialize(entity) 将从数据库加载该实体的完整对象图。但是,如果只需要具有每个代理设置的 ID 的真实类,则似乎更加复杂。 - djmj
截至Hibernate 4.x,这仍然是一个有效的解决方案吗?根据我阅读的文档,有通过XML或注释禁用实体的延迟加载的方法。请给予建议。(http://docs.jboss.org/hibernate/orm/4.3/manual/en-US/html_single/#persistent-classes-pojo-final-example-disable-proxies-xml) - Satya
9
不需要使用自己的util类,也可以实现相同功能 - (T)Hibernate.unproxy(entity) - panser
显示剩余8条评论

87

自从Hibernate ORM 5.2.10版本以后,您可以像这样做:

Object unproxiedEntity = Hibernate.unproxy(proxy);

在 Hibernate 5.2.10 之前,最简单的方法是使用 Hibernate 内部 PersistenceContext 实现提供的 unproxy 方法:

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

调用此方法时,是否处理父实体的集合字段?例如,如果您有一个具有Student列表的Department,是否仍然需要unproxy(department.getStudents()),或者只需unproxy(department)即可? - trafalmadorian
2
只有给定的代理被初始化。它不会级联到关联对象,因为如果您解除代理根实体,则可能会加载大量数据。 - Vlad Mihalcea
然而,如果代理未初始化,则PersistentContext#unproxy(proxy)会抛出异常,而Hibernate.unproxy(proxy)LazyInitializer#getImplementation(proxy)会在必要时初始化代理。因为这种差异,我刚刚捕获了一个异常。;-) - bgraves
你如何取消代理一个实体集合? - masber
同样的方法,它也适用于实体集合。 - Vlad Mihalcea

17

尝试使用Hibernate.getClass(obj)


17
返回的是类而不是去代理化后的对象本身。 - Stefan Haberl
3
实际上,当我们尝试查找obj的类以进行instanceof比较时,这个解决方案非常好。 - João Rebelo
请注意Javadoc中的说明:“此操作将通过副作用初始化代理。” - snorbi

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服务的结果(通过切面),它会递归地清理所有未初始化的代理对象。请注意保留HTML标签。

感谢分享这段代码,虽然它没有涵盖所有的用例,但确实非常有帮助。 - Prateek Singh
1
正确。它应该根据新情况进行更新。您可以尝试GWT团队推荐的方法。请看这里:http://www.gwtproject.org/articles/using_gwt_with_hibernate.html(参见Integration Strategies部分)。总的来说,他们建议使用DTO或Dozer或Gilead。如果您能提供您的意见,那就太好了。在我的情况下,我的代码看起来是最简单的解决方案,但不完整 =(。 - Sergey Bondarev
谢谢。我们从哪里可以获取“CollectionsUtils.containsTotallyEqual(handledObjects,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
这只是我自己创建的实用方法。 - Sergey Bondarev

10

我建议使用JPA 2的方式:

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

2
你的答案和我的有什么不同? - Vlad Mihalcea
我尝试过这个解决方案...如果在取消包装命令之前不像这样放置一些内容,它并不总是有效:HibernateProxy hibernateProxy = (HibernateProxy)possibleProxyObject; 如果(hibernateProxy.getHibernateLazyInitializer().isUninitialized()){ hibernateProxy.getHibernateLazyInitializer().initialize(); } - user3227576

5

Hiebrnate 5.2.10开始,您可以使用Hibernate.proxy方法将代理转换为实际实体:

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

2
使用Spring Data JPA和Hibernate时,我使用JpaRepository的子接口来查找属于类型继承结构的对象,该结构使用“join”策略进行映射。不幸的是,这些查询返回的是基础类型的代理而不是预期的具体类型实例。这阻止了我将结果强制转换为正确的类型。像您一样,我也在寻找一种有效的方法来取消代理实体。
Vlad提供了解除代理这些结果的正确方法;Yannis提供了更多详细信息。补充他们的答案,以下是您可能正在寻找的其余部分:
以下代码提供了一个简单的方法来取消代理代理实体:
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方法。如果它们已经是未代理的,它们将被简单地返回。否则,它们将被取消代理并返回。
希望这能帮到你!

2
另一个解决方法是调用:
Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

在关闭会话之前。


1
感谢您提供的解决方案!不幸的是,对于我的情况都没有起作用:通过JPA-Hibernate从Oracle数据库接收CLOB对象列表,使用本地查询。
所有建议的方法都给我返回了ClassCastException或者仅返回java代理对象(其内部深层包含所需的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();
}

希望这能帮助到某些人!

1
我找到了一种使用标准Java和JPA API去代理一个类的解决方案。已经测试过hibernate,但不需要作为依赖项,并且应该适用于所有JPA提供程序。
唯一要求是必须修改父类(Address)并添加一个简单的辅助方法。
总体思路:向父类添加辅助方法,该方法返回自身。当在代理上调用方法时,它将转发调用到真实实例并返回此真实实例。
实现略微复杂,因为hibernate识别代理类返回自身并仍然返回代理而不是真实实例。解决方法是将返回的实例包装到一个简单的包装器类中,该类具有与真实实例不同的类类型。
代码如下:
class Address {
   public AddressWrapper getWrappedSelf() {
       return new AddressWrapper(this);
   }
...
}

class AddressWrapper {
    private Address wrappedAddress;
...
}

要将地址代理转换为真实子类,请使用以下内容:

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

你的示例代码似乎有点不清楚(或者可能是我需要更多的咖啡)。EntityWrapper从哪里来?应该是AddressWrapper吗?我猜AddressWrapped应该说AddressWrapper?你能澄清一下吗? - Gus
@Gus,你是对的。我已经更正了这个例子。谢谢 :) - OndroMih

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接