Como converter um proxy Hibernate em um objeto de entidade real

161

Durante um Hibernate Session, estou carregando alguns objetos e alguns deles são carregados como proxies devido ao carregamento lento. Está tudo bem e não quero desativar o carregamento lento.

Mais tarde, porém, preciso enviar alguns dos objetos (na verdade um objeto) para o cliente GWT via RPC. E acontece que esse objeto concreto é um proxy. Então, eu preciso transformá-lo em um objeto real. Não consigo encontrar um método como "materializar" no Hibernate.

Como posso transformar alguns objetos de proxies em reais sabendo sua classe e ID?

No momento, a única solução que vejo é despejar esse objeto do cache do Hibernate e recarregá-lo, mas é muito ruim por vários motivos.

Andrey Minogin
fonte

Respostas:

232

Aqui está um método que estou 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
fonte
1
Como eu queria fazer a mesma coisa, escrevi a instância em proxy para um ObjectOutputStream e depois a li novamente de um ObjectInputStream correspondente, e isso pareceu funcionar. Não tenho certeza se é uma abordagem eficiente, mas ainda me pergunto por que funcionou ... qualquer comentário sobre ela será muito apreciado. Obrigado!
shrini1000
@ shrini1000 funcionou porque ao serializar inicializa a coleção (se a sessão ainda não estiver fechada). Também HibernateProxydefine um writeReplacemétodo para forçar os implementadores a fazer algo especial durante a serialização.
Bozho 8/03/11
1
Existe uma maneira portátil (JPA) de fazer isso?
Kawu
por que, Hibernate.initialize lançando lazyInitializeException quando eu a chamo? Estou apenas usando como: Object o = session.get (MyClass.class, id); Objeto outro = o.getSomeOtherClass (); initializeAndUnproxy (outro);
fredcrs
6
você pode fazer o mesmo sem a sua própria classe util -(T)Hibernate.unproxy(entity)
Panser
46

Como expliquei neste artigo , desde o Hibernate ORM 5.2.10 , você pode fazer o seguinte:

Object unproxiedEntity = Hibernate.unproxy(proxy);

Antes do Hibernate 5.2.10 . a maneira mais simples de fazer isso era usar o método não- oxigenado oferecido pela PersistenceContextimplementação interna do Hibernate :

Object unproxiedEntity = ((SessionImplementor) session)
                         .getPersistenceContext()
                         .unproxy(proxy);
Vlad Mihalcea
fonte
Chamar isso em uma entidade pai manipula campos de coleta? por exemplo, se você possui uma Departmentlista com Student, ainda precisa unproxy(department.getStudents()) - ou é suficiente unproxy(department)?
Trafalmadorian #
1
Somente o Proxy fornecido é inicializado. Ele não faz cascata para associações, pois isso pode potencialmente carregar toneladas de dados se você desoxi-se uma entidade raiz.
28819 Vlad Mihalcea
No entanto, PersistentContext#unproxy(proxy)lança uma exceção se o proxy não for inicializado enquanto Hibernate.unproxy(proxy)e, LazyInitializer#getImplementation(proxy)se necessário, inicialize o proxy. Só peguei uma exceção devido a essa diferença. ;-)
bgraves
13

Tente usar Hibernate.getClass(obj)

Sanek Shu
fonte
15
Isso retorna a classe em vez do próprio objeto deproxied
Stefan Haberl
Na verdade, essa solução é ótima quando estamos tentando encontrar a classe de obj, por exemplo, de comparações.
João Rebelo
13

Eu escrevi o seguinte código que limpa o objeto de proxies (se eles ainda não estiverem 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());
    }

}

Eu uso essa função sobre o resultado dos meus serviços RPC (por meio de aspectos) e limpa recursivamente todos os objetos de resultado dos proxies (se não forem inicializados).

Sergey Bondarev
fonte
obrigado por compartilhar este código embora não tenha coberto todos os casos casos de uso, mas realmente útil ...
Prateek Singh
Corrigir. Deve ser atualizado de acordo com novos casos. Você pode tentar as coisas recomendadas pelos caras da GWT. Veja aqui: gwtproject.org/articles/using_gwt_with_hibernate.html (consulte a parte Estratégias de integração). Em geral, eles recomendam o uso de DTO, Dozer ou Gilead. Tudo bem se você der sua opinião sobre isso. No meu caso, parece que meu código é a solução mais simples, mas não está completa = (.
Sergey Bondarev
obrigado. onde podemos obter uma implementação para "CollectionsUtils.containsTotallyEqual (handledObjects, value)"?
precisa saber é o seguinte
boolean estático público containsTotallyEqual (coleção <?> coleção, valor do objeto) {if (isEmpty (coleção)) {return false; } for (Objeto do objeto: coleção) {if (object == value) {return true; } } retorna falso; }
Sergey Bondarev
É apenas um método utilitário criado por mim mesmo
Sergey Bondarev 5/15
10

Da maneira que eu recomendo com o JPA 2:

Object unproxied  = entityManager.unwrap(SessionImplementor.class).getPersistenceContext().unproxy(proxy);
Yannis JULIENNE
fonte
2
Qual é a sua resposta diferente da minha?
Vlad Mihalcea
Eu tentei esta solução ... nem sempre funciona se você não colocar algo assim antes do comando desembrulhar: HibernateProxy hibernateProxy = (HibernateProxy) possibleProxyObject; if (hibernateProxy.getHibernateLazyInitializer (). isUninitialized ()) {hibernateProxy.getHibernateLazyInitializer (). initialize (); }
user3227576
2

Com o Spring Data JPA e o Hibernate, eu estava usando subinterfaces de JpaRepositorypara pesquisar objetos pertencentes a uma hierarquia de tipos que foi mapeada usando a estratégia "join". Infelizmente, as consultas estavam retornando proxies do tipo base em vez de instâncias dos tipos concretos esperados. Isso me impediu de transmitir os resultados para os tipos corretos. Como você, eu vim aqui procurando uma maneira eficaz de fazer com que minhas entidades não sejam afetadas.

Vlad tem a idéia certa para desestabilizar esses resultados; Yannis fornece um pouco mais de detalhes. Além das respostas, aqui está o restante do que você pode estar procurando:

O código a seguir fornece uma maneira fácil de desoxirar suas entidades com 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;
    }

}

Você pode transmitir entidades não-autorizadas ou entidades com proxy para o unproxymétodo. Se eles já estiverem sem toxina, eles simplesmente serão devolvidos. Caso contrário, eles ficarão sem proteção e retornados.

Espero que isto ajude!

Sharky
fonte
1

A outra solução alternativa é chamar

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Pouco antes de fechar a sessão.

0x6B6F77616C74
fonte
1

Encontrei uma solução para deproxy uma classe usando API Java e JPA padrão. Testado com hibernação, mas não requer hibernação como uma dependência e deve funcionar com todos os provedores de JPA.

Um único requisito - é necessário modificar a classe pai (Endereço) e adicionar um método auxiliar simples.

Ideia geral: adicione o método auxiliar à classe pai que retorna automaticamente. Quando o método é chamado no proxy, ele encaminhará a chamada para a instância real e retornará essa instância real.

A implementação é um pouco mais complexa, pois o hibernate reconhece que a classe em proxy retorna a si mesma e ainda retorna o proxy em vez da instância real. A solução alternativa é agrupar a instância retornada em uma classe de wrapper simples, que possui um tipo de classe diferente da instância real.

Em código:

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

class AddressWrapper {
    private Address wrappedAddress;
...
}

Para converter o proxy de endereço na subclasse real, use o seguinte:

Address address = dao.getSomeAddress(...);
Address deproxiedAddress = address.getWrappedSelf().getWrappedAddress();
if (deproxiedAddress instanceof WorkAddress) {
WorkAddress workAddress = (WorkAddress)deproxiedAddress;
}
OndroMih
fonte
Seu código de exemplo parece um pouco obscuro (ou talvez eu só precise de mais café). De onde vem o EntityWrapper? deve ser AddressWrapper? E acho que AddressWrapped deve dizer AddressWrapper? Você pode esclarecer isso?
Gus
@ Gus, você está certo. Eu corrigi o exemplo. Obrigado :)
OndroMih
1

A partir do Hiebrnate 5.2.10, você pode usar o método Hibernate.proxy para converter um proxy em sua entidade real:

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

Obrigado pelas soluções sugeridas! Infelizmente, nenhum deles funcionou para o meu caso: receber uma lista de objetos CLOB do banco de dados Oracle por meio do JPA - Hibernate, usando uma consulta nativa.

Todas as abordagens propostas me deram um ClassCastException ou apenas retornaram o objeto java Proxy (que continha profundamente o Clob desejado).

Portanto, minha solução é a seguinte (com base nas várias abordagens acima):

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 isso ajude alguém!

Dmitry
fonte