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.

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

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

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)


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).


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

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!


1

A outra solução alternativa é chamar

Hibernate.initialize(extractedObject.getSubojbectToUnproxy());

Pouco antes de fechar a sessão.


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

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


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!

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.