ChatGPT解决这个技术问题 Extra ChatGPT

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

在休眠 Session 期间,我正在加载一些对象,其中一些由于延迟加载而被加载为代理。一切都好,我不想关闭延迟加载。

但后来我需要通过 RPC 将一些对象(实际上是一个对象)发送到 GWT 客户端。碰巧这个具体的对象是一个代理。所以我需要把它变成一个真实的物体。我在 Hibernate 中找不到像“物化”这样的方法。

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

目前我看到的唯一解决方案是从 Hibernate 的缓存中删除该对象并重新加载它,但由于许多原因,它确实很糟糕。


B
Bozho

这是我正在使用的一种方法。

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

我想做同样的事情,所以我将代理实例写入 ObjectOutputStream,然后从相应的 ObjectInputStream 中读取它,这似乎起到了作用。我不确定这是否是一种有效的方法,但仍然想知道它为什么会起作用……对此的任何评论将不胜感激。谢谢!
shrini1000 它起作用了,因为在序列化时会初始化集合(如果会话尚未关闭)。此外,HibernateProxy 定义了一个 writeReplace 方法来强制实现者在序列化期间做一些特殊的事情。
是否有可移植(JPA)的方式来做到这一点?
为什么当我调用 Hibernate.initialize 时会抛出lazyInitializeException?我只是使用类似: Object o = session.get(MyClass.class, id);对象其他 = o.getSomeOtherClass();初始化和取消代理(其他);
您可以在没有自己的 util 类的情况下做同样的事情 - (T)Hibernate.unproxy(entity)
V
Vlad Mihalcea

从 Hibernate ORM 5.2.10 开始,您可以这样做:

Object unproxiedEntity = Hibernate.unproxy(proxy);

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

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

在父实体上调用它是否处理集合字段?例如,如果您有一个列表为 StudentDepartment,您还需要 unproxy(department.getStudents()) - 还是只需 unproxy(department) 就足够了?
只有给定的代理被初始化。它不会级联到关联,因为如果您碰巧取消代理根实体,这可能会加载大量数据。
但是,如果代理未初始化,则 PersistentContext#unproxy(proxy) 会引发异常,而 Hibernate.unproxy(proxy)LazyInitializer#getImplementation(proxy) 会在必要时初始化代理。由于这种差异,刚刚捕获了一个异常。 ;-)
您将如何取消代理实体集合?
一样的方法。它也应该适用于实体集合。
J
Juan Mellado

尝试使用 Hibernate.getClass(obj)


这将返回类而不是被代理的对象本身
实际上,当我们试图找到 obj 的 Class 以进行 instanceof 比较时,这个解决方案非常棒。
请注意,javadoc 声明:“此操作将通过副作用初始化代理。”
K
Kerem Baydoğan

我编写了以下代码从代理中清除对象(如果它们尚未初始化)

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 服务的结果上使用这个函数(通过方面),它递归地清除代理中的所有结果对象(如果它们没有被初始化)。


感谢分享这段代码,虽然它没有涵盖所有用例,但它真的很有帮助......
正确的。它应该根据新的情况进行更新。你可以试试 GWT 推荐的东西。看这里:gwtproject.org/articles/using_gwt_with_hibernate.html(参见集成策略部分)。一般来说,他们建议使用 DTO 或 Dozer 或 Gilead。如果你能就此发表你的意见就好了。就我而言,它看起来我的代码是最简单的解决方案,但不是完整的 =(.
谢谢。我们在哪里可以获得“CollectionsUtils.containsTotallyEqual(handledObjects, value)”的实现?
public static boolean containsTotallyEqual(Collection 集合,对象值) { if (isEmpty(collection)) { return false; } for (Object object : collection) { if (object == value) { return true; } } 返回假; }
这只是我自己创建的实用方法
Y
Yannis JULIENNE

我推荐 JPA 2 的方式:

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

你的答案和我的有什么不同?
我已经尝试过这个解决方案......如果你不在 unwrap-command 之前放置这样的东西,它并不总是有效: HibernateProxy hibernateProxy = (HibernateProxy)possibleProxyObject; if (hibernateProxy.getHibernateLazyInitializer().isUninitialized()){ hibernateProxy.getHibernateLazyInitializer().initialize(); }
O
O.Badr

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

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

0
0x6B6F77616C74

另一种解决方法是调用

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

就在关闭会话之前。


S
Sharky

使用 Spring Data JPA 和 Hibernate,我使用 JpaRepository 的子接口来查找属于使用“连接”策略映射的类型层次结构的对象。不幸的是,查询返回的是基本类型的代理,而不是预期的具体类型的实例。这使我无法将结果转换为正确的类型。和你一样,我来这里是为了寻找一种有效的方法来让我的实体不受代理。

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 方法。如果它们已经未被代理,它们将被简单地返回。否则,他们将被取消代理并返回。

希望这可以帮助!


D
Dmitry

感谢您提出的解决方案!不幸的是,它们都不适用于我的情况:使用本机查询,通过 JPA - Hibernate 从 Oracle 数据库接收 CLOB 对象列表。

所有提议的方法都给了我一个 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();
}

希望这会对某人有所帮助!


O
OndroMih

我找到了使用标准 Java 和 JPA API 代理类的解决方案。使用 hibernate 进行测试,但不需要将 hibernate 作为依赖项,并且应该与所有 JPA 提供程序一起使用。

只有一个要求 - 需要修改父类(地址)并添加一个简单的辅助方法。

总体思路:将辅助方法添加到返回自身的父类。当在代理上调用方法时,它将调用转发给真实实例并返回这个真实实例。

实现稍微复杂一点,因为 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,你是对的。我纠正了这个例子。谢谢 :)