在休眠 Session
期间,我正在加载一些对象,其中一些由于延迟加载而被加载为代理。一切都好,我不想关闭延迟加载。
但后来我需要通过 RPC 将一些对象(实际上是一个对象)发送到 GWT 客户端。碰巧这个具体的对象是一个代理。所以我需要把它变成一个真实的物体。我在 Hibernate 中找不到像“物化”这样的方法。
如何将某些对象从代理转换为知道它们的类和 ID 的真实?
目前我看到的唯一解决方案是从 Hibernate 的缓存中删除该对象并重新加载它,但由于许多原因,它确实很糟糕。
这是我正在使用的一种方法。
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;
}
从 Hibernate ORM 5.2.10 开始,您可以这样做:
Object unproxiedEntity = Hibernate.unproxy(proxy);
在休眠 5.2.10 之前。最简单的方法是使用 Hibernate 内部 PersistenceContext
实现提供的 unproxy 方法:
Object unproxiedEntity = ((SessionImplementor) session)
.getPersistenceContext()
.unproxy(proxy);
Student
的 Department
,您还需要 unproxy(department.getStudents())
- 还是只需 unproxy(department)
就足够了?
PersistentContext#unproxy(proxy)
会引发异常,而 Hibernate.unproxy(proxy)
和 LazyInitializer#getImplementation(proxy)
会在必要时初始化代理。由于这种差异,刚刚捕获了一个异常。 ;-)
尝试使用 Hibernate.getClass(obj)
我编写了以下代码从代理中清除对象(如果它们尚未初始化)
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 服务的结果上使用这个函数(通过方面),它递归地清除代理中的所有结果对象(如果它们没有被初始化)。
我推荐 JPA 2 的方式:
Object unproxied = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().unproxy(proxy);
从 Hiebrnate 5.2.10 开始,您可以使用 Hibernate.proxy 方法将代理转换为您的真实实体:
MyEntity myEntity = (MyEntity) Hibernate.unproxy( proxyMyEntity );
另一种解决方法是调用
Hibernate.initialize(extractedObject.getSubojbectToUnproxy());
就在关闭会话之前。
使用 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
方法。如果它们已经未被代理,它们将被简单地返回。否则,他们将被取消代理并返回。
希望这可以帮助!
感谢您提出的解决方案!不幸的是,它们都不适用于我的情况:使用本机查询,通过 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();
}
希望这会对某人有所帮助!
我找到了使用标准 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;
}
不定期副业成功案例分享
HibernateProxy
定义了一个writeReplace
方法来强制实现者在序列化期间做一些特殊的事情。(T)Hibernate.unproxy(entity)