Cómo convertir un proxy de Hibernate en un objeto de entidad real

161

Durante una hibernación Session, estoy cargando algunos objetos y algunos de ellos se cargan como servidores proxy debido a la carga diferida. Todo está bien y no quiero desactivar la carga diferida.

Pero luego necesito enviar algunos de los objetos (en realidad un objeto) al cliente GWT a través de RPC. Y sucede que este objeto concreto es un proxy. Entonces necesito convertirlo en un objeto real. No puedo encontrar un método como "materializar" en Hibernate.

¿Cómo puedo convertir algunos de los objetos de proxies a reales conociendo su clase e ID?

Por el momento, la única solución que veo es desalojar ese objeto del caché de Hibernate y volver a cargarlo, pero es realmente malo por muchas razones.

Andrey Minogin
fuente

Respuestas:

232

Aquí hay un método que estoy usando.

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
fuente
1
Quería hacer lo mismo, así que escribí la instancia proxy en un ObjectOutputStream y luego la leí de nuevo desde un ObjectInputStream correspondiente, y eso pareció funcionar. No estoy seguro de si es un enfoque eficiente, pero aún me pregunto por qué funcionó ... cualquier comentario al respecto será muy apreciado. ¡Gracias!
shrini1000
@ shrini1000 funcionó porque al serializar se inicializa la colección (si la sesión aún no está cerrada). También HibernateProxydefine un writeReplacemétodo para obligar a los implementadores a hacer algo especial durante la serialización.
Bozho
1
¿Hay alguna forma portátil (JPA) de hacer esto?
Kawu
¿por qué Hibernate.initialize lanza lazyInitializeException cuando lo llamo? Solo estoy usando like: Object o = session.get (MyClass.class, id); Objeto otro = o.getSomeOtherClass (); initializeAndUnproxy (otro);
Fredred
66
puede hacer lo mismo sin su propia clase de utilidades -(T)Hibernate.unproxy(entity)
analizador
46

Como he explicado en este artículo , ya que Hibernate ORM 5.2.10 , puede hacerlo LiKee esto:

Object unproxiedEntity = Hibernate.unproxy(proxy);

Antes de Hibernate 5.2.10 . La forma más sencilla de hacerlo era utilizar el método no proxy ofrecido por la PersistenceContextimplementación interna de Hibernate :

Object unproxiedEntity = ((SessionImplementor) session)
                         .getPersistenceContext()
                         .unproxy(proxy);
Vlad Mihalcea
fuente
¿Llamar a esto en una entidad principal maneja los campos de colección? p. ej., si tiene una DepartmentLista de Student, ¿todavía necesita unproxy(department.getStudents()) o es suficiente unproxy(department)?
trafalmadorian
1
Solo se inicializa el Proxy dado. No se conecta en cascada a las asociaciones, ya que eso podría cargar toneladas de datos si desproxy una entidad raíz.
Vlad Mihalcea
Sin embargo, PersistentContext#unproxy(proxy)arroja una excepción si el proxy no se inicializa Hibernate.unproxy(proxy)y, LazyInitializer#getImplementation(proxy)si es necesario, lo inicializa. Acabo de atrapar una excepción debido a esta diferencia. ;-)
bgraves
13

Tratar de usar Hibernate.getClass(obj)

Sanek Shu
fuente
15
Esto devuelve la clase en lugar del objeto desproxiado en sí mismo
Stefan Haberl
En realidad, esta solución es excelente cuando tratamos de encontrar la clase de obj, por ejemplo, de comparaciones.
João Rebelo
13

He escrito el siguiente código que limpia el objeto de los servidores proxy (si aún no están inicializados)

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

}

Utilizo esta función sobre el resultado de mis servicios RPC (a través de aspectos) y limpia recursivamente todos los objetos de resultados de los servidores proxy (si no están inicializados).

Sergey Bondarev
fuente
gracias por compartir este código, aunque no ha cubierto todos los casos de uso, pero es realmente útil ...
Prateek Singh
Correcto. Debe actualizarse de acuerdo con nuevos casos. Podrías probar cosas recomendadas por los chicos de GWT. Mire aquí: gwtproject.org/articles/using_gwt_with_hibernate.html (consulte la parte Estrategias de integración). En general, recomiendan usar DTO o Dozer o Gilead. Estará bien si da su opinión sobre esto. En mi caso, parece que mi código es la solución más simple, pero no completa = (.
Sergey Bondarev
Gracias. ¿Dónde podemos obtener una implementación para "CollectionsUtils.containsTotallyEqual (handleObjects, value)"?
Ilan.K
public boolean estático contieneTotallyEqual (Collection <?> collection, Object value) {if (isEmpty (collection)) {return false; } for (Object object: collection) {if (object == value) {return true; } } falso retorno; }
Sergey Bondarev
Es solo un método de utilidad creado por mí mismo
Sergey Bondarev
10

La forma en que recomiendo con JPA 2:

Object unproxied  = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().unproxy(proxy);
Yannis JULIENNE
fuente
2
¿Cómo es tu respuesta diferente a la mía?
Vlad Mihalcea
He intentado esta solución ... no funciona siempre si no pone algo como esto antes del comando de desenvolver: HibernateProxy hibernateProxy = (HibernateProxy) possibleProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initialize (); }
user3227576
2

Con Spring Data JPA e Hibernate, estaba usando subinterfaces de JpaRepositorypara buscar objetos que pertenecen a una jerarquía de tipos que fue mapeada usando la estrategia de "unión". Desafortunadamente, las consultas estaban devolviendo proxys del tipo base en lugar de instancias de los tipos concretos esperados. Esto me impidió enviar los resultados a los tipos correctos. Al igual que tú, vine aquí buscando una manera efectiva de liberar mis intereses.

Vlad tiene la idea correcta para eliminar estos resultados; Yannis proporciona un poco más de detalle. Además de sus respuestas, aquí está el resto de lo que podría estar buscando:

El siguiente código proporciona una manera fácil de desproxiar sus entidades proxy:

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

}

Puede pasar entidades no representadas o entidades representadas al unproxymétodo. Si ya no están representados, simplemente serán devueltos. De lo contrario, no serán representados y serán devueltos

¡Espero que esto ayude!

Sharky
fuente
1

La otra solución es llamar

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Justo antes de cerrar la sesión.

0x6B6F77616C74
fuente
1

Encontré una solución para desproxiar una clase usando Java estándar y API JPA. Probado con hibernación, pero no requiere hibernación como dependencia y debería funcionar con todos los proveedores de JPA.

Un único requisito: es necesario modificar la clase principal (Dirección) y agregar un método auxiliar simple.

Idea general: agregar un método auxiliar a la clase padre que se devuelve. cuando el método se llama al proxy, reenviará la llamada a la instancia real y devolverá esta instancia real.

La implementación es un poco más compleja, ya que hibernate reconoce que la clase proxy se devuelve a sí misma y aún devuelve proxy en lugar de una instancia real. La solución consiste en ajustar la instancia devuelta en una clase de contenedor simple, que tiene un tipo de clase diferente que la instancia real.

En codigo:

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

class AddressWrapper {
    private Address wrappedAddress;
...
}

Para enviar el proxy de dirección a una subclase real, use lo siguiente:

Address address = dao.getSomeAddress(...);
Address deproxiedAddress = address.getWrappedSelf().getWrappedAddress();
if (deproxiedAddress instanceof WorkAddress) {
WorkAddress workAddress = (WorkAddress)deproxiedAddress;
}
OndroMih
fuente
Su código de ejemplo parece un poco confuso (o tal vez solo necesito más café). ¿De dónde viene EntityWrapper? debería ser AddressWrapper? Y supongo que AddressWrapped debería decir AddressWrapper? ¿Puedes aclarar esto?
Gus
@ Gus, tienes razón. Corrija el ejemplo. Gracias :)
OndroMih
1

A partir de Hiebrnate 5.2.10 , puede usar el método Hibernate.proxy para convertir un proxy a su entidad real:

MyEntity myEntity = (MyEntity) Hibernate.unproxy( proxyMyEntity );
O.Badr
fuente
0

¡Gracias por las soluciones sugeridas! Desafortunadamente, ninguno de ellos funcionó para mi caso: recibir una lista de objetos CLOB de la base de datos Oracle a través de JPA - Hibernate, usando una consulta nativa.

Todos los enfoques propuestos me dieron una ClassCastException o simplemente devolvieron el objeto Proxy java (que contenía profundamente el Clob deseado).

Entonces mi solución es la siguiente (basada en varios enfoques anteriores):

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

Espero que esto ayude a alguien!

Dmitry
fuente