Hibernate: prática recomendada para extrair todas as coleções lazy

92

O que eu tenho:

@Entity
public class MyEntity {
  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Address> addreses;

  @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
  @JoinColumn(name = "myentiy_id")
  private List<Person> persons;

  //....
}

public void handle() {

   Session session = createNewSession();
   MyEntity entity = (MyEntity) session.get(MyEntity.class, entityId);
   proceed(session); // FLUSH, COMMIT, CLOSE session!

   Utils.objectToJson(entity); //TROUBLES, because it can't convert to json lazy collections
}

Que problema:

O problema é que não consigo puxar a coleção preguiçosa após o encerramento da sessão. Mas também não consigo fechar uma sessão no método de procedimento .

Que solução (solução grosseira):

a) Antes de a sessão ser encerrada, force a hibernação para extrair coleções preguiçosas

entity.getAddresses().size();
entity.getPersons().size();

....

b) Talvez uma maneira mais elegante seja usar @Fetch(FetchMode.SUBSELECT)anotações

Questão:

Qual é a melhor prática / maneira comum / maneira mais elegante de fazer isso? Significa converter meu objeto em JSON.

VB_
fonte

Respostas:

102

Use Hibernate.initialize()dentro @Transactionalpara inicializar objetos preguiçosos.

 start Transaction 
      Hibernate.initialize(entity.getAddresses());
      Hibernate.initialize(entity.getPersons());
 end Transaction 

Agora, fora da transação, você pode obter objetos preguiçosos.

entity.getAddresses().size();
entity.getPersons().size();
Prabhakaran Ramaswamy
fonte
1
Parece atraente). Pelo que entendi, se vou usar @Fetch (FetchMode.SUBSELECT), posso chamar Hibernate.initialize apenas uma vez para extrair todas as coleções. Estou certo?
VB_
4
E como você gerencia quando recupera uma coleção do MyEntity?
Alexis Dufrenoy
1
Se você chamar qualquer método como "size ()" em uma coleção em uma transação, ele também irá inicializá-lo, de forma que seu exemplo após a inicialização não seja o melhor. Dito isso, "Hibernate.initialize (...)" é semanticamente melhor do que collection.size (), então você tem o melhor conselho.
Tristan
7

Você pode percorrer os Getters do objeto Hibernate na mesma transação para garantir que todos os objetos filho preguiçosos sejam buscados avidamente com a seguinte classe auxiliar genérica :

HibernateUtil.initializeObject (myObject, "my.app.model");

package my.app.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import org.aspectj.org.eclipse.jdt.core.dom.Modifier;
import org.hibernate.Hibernate;

public class HibernateUtil {

public static byte[] hibernateCollectionPackage = "org.hibernate.collection".getBytes();

public static void initializeObject( Object o, String insidePackageName ) {
    Set<Object> seenObjects = new HashSet<Object>();
    initializeObject( o, seenObjects, insidePackageName.getBytes() );
    seenObjects = null;
}

private static void initializeObject( Object o, Set<Object> seenObjects, byte[] insidePackageName ) {

    seenObjects.add( o );

    Method[] methods = o.getClass().getMethods();
    for ( Method method : methods ) {

        String methodName = method.getName();

        // check Getters exclusively
        if ( methodName.length() < 3 || !"get".equals( methodName.substring( 0, 3 ) ) )
            continue;

        // Getters without parameters
        if ( method.getParameterTypes().length > 0 )
            continue;

        int modifiers = method.getModifiers();

        // Getters that are public
        if ( !Modifier.isPublic( modifiers ) )
            continue;

        // but not static
        if ( Modifier.isStatic( modifiers ) )
            continue;

        try {

            // Check result of the Getter
            Object r = method.invoke( o );

            if ( r == null )
                continue;

            // prevent cycles
            if ( seenObjects.contains( r ) )
                continue;

            // ignore simple types, arrays und anonymous classes
            if ( !isIgnoredType( r.getClass() ) && !r.getClass().isPrimitive() && !r.getClass().isArray() && !r.getClass().isAnonymousClass() ) {

                // ignore classes out of the given package and out of the hibernate collection
                // package
                if ( !isClassInPackage( r.getClass(), insidePackageName ) && !isClassInPackage( r.getClass(), hibernateCollectionPackage ) ) {
                    continue;
                }

                // initialize child object
                Hibernate.initialize( r );

                // traverse over the child object
                initializeObject( r, seenObjects, insidePackageName );
            }

        } catch ( InvocationTargetException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalArgumentException e ) {
            e.printStackTrace();
            return;
        } catch ( IllegalAccessException e ) {
            e.printStackTrace();
            return;
        }
    }

}

private static final Set<Class<?>> IGNORED_TYPES = getIgnoredTypes();

private static boolean isIgnoredType( Class<?> clazz ) {
    return IGNORED_TYPES.contains( clazz );
}

private static Set<Class<?>> getIgnoredTypes() {
    Set<Class<?>> ret = new HashSet<Class<?>>();
    ret.add( Boolean.class );
    ret.add( Character.class );
    ret.add( Byte.class );
    ret.add( Short.class );
    ret.add( Integer.class );
    ret.add( Long.class );
    ret.add( Float.class );
    ret.add( Double.class );
    ret.add( Void.class );
    ret.add( String.class );
    ret.add( Class.class );
    ret.add( Package.class );
    return ret;
}

private static Boolean isClassInPackage( Class<?> clazz, byte[] insidePackageName ) {

    Package p = clazz.getPackage();
    if ( p == null )
        return null;

    byte[] packageName = p.getName().getBytes();

    int lenP = packageName.length;
    int lenI = insidePackageName.length;

    if ( lenP < lenI )
        return false;

    for ( int i = 0; i < lenI; i++ ) {
        if ( packageName[i] != insidePackageName[i] )
            return false;
    }

    return true;
}
}
Florian Sager
fonte
Obrigado por esta resposta. Eu sei que já faz um tempo, mas eu estava tentando resolver isso e demorou muito até que li seu código aqui. Também adicionei ifs ao início do segundo método initializeObject (object, seenObjects, insidePackageName): Iterar if (object instanceof List) { for(Object item : (List<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } else if (object instanceof Set) { for(Object item : (Set<Object>) object) { initializeObject(item, seenObjects, insidePackageName); } return; } listas de outra forma ignoradas.
Chip de
E se SecurityException for lançado em o.getClass (). GetMethods () ;?
Oleksii Kyslytsyn
6

Não é a melhor solução, mas aqui está o que consegui:

1) Anote o getter que deseja inicializar com esta anotação:

@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {

}

2) Use este método (pode ser colocado em uma classe genérica ou você pode alterar T com a classe Object) em um objeto depois de lê-lo do banco de dados:

    public <T> void forceLoadLazyCollections(T entity) {

    Session session = getSession().openSession();
    Transaction tx = null;
    try {

        tx = session.beginTransaction();
        session.refresh(entity);
        if (entity == null) {
            throw new RuntimeException("Entity is null!");
        }
        for (Method m : entityClass.getMethods()) {

            Lazy annotation = m.getAnnotation(Lazy.class);
            if (annotation != null) {
                m.setAccessible(true);
                logger.debug(" method.invoke(obj, arg1, arg2,...); {} field", m.getName());
                try {
                    Hibernate.initialize(m.invoke(entity));
                }
                catch (Exception e) {
                    logger.warn("initialization exception", e);
                }
            }
        }

    }
    finally {
        session.close();
    }
}
Damian
fonte
Eu uso session.refresh em uma iteração para carregar lazyCollections. e sempre que executo meu programa apenas para uma de minhas entidades, carrego a coleção LazyInitializationException e outras, depois de chamar session.refresh. Como isso pôde acontecer
saba safavi
5

Coloque o Utils.objectToJson (entidade); ligue antes do encerramento da sessão.

Ou você pode tentar definir o modo de busca e brincar com um código como este

Session s = ...
DetachedCriteria dc = DetachedCriteria.forClass(MyEntity.class).add(Expression.idEq(id));
dc.setFetchMode("innerTable", FetchMode.EAGER);
Criteria c = dc.getExecutableCriteria(s);
MyEntity a = (MyEntity)c.uniqueResult();
StanislavL
fonte
FetchMode.EAGER está obsoleto. O javadoc recomenda usar FetchMode.JOIN, agora.
Alexis Dufrenoy
4

Com o Hibernate 4.1.6, um novo recurso é introduzido para lidar com esses problemas de associação preguiçosa. Ao habilitar a propriedade hibernate.enable_lazy_load_no_trans em hibernate.properties ou em hibernate.cfg.xml, você não terá mais LazyInitializationException.

Para mais, consulte: https://stackoverflow.com/a/11913404/286588

Fazenda
fonte
3
Na verdade, este é um antipadrão. Para mais informações: vladmihalcea.com/…
Ph03n1x
3

Ao buscar várias coleções, você precisa:

  1. JOIN FETCH uma coleção
  2. Use o Hibernate.initializepara as coleções restantes.

Portanto, no seu caso, você precisa de uma primeira consulta JPQL como esta:

MyEntity entity = session.createQuery("select e from MyEntity e join fetch e.addreses where e.id 
= :id", MyEntity.class)
.setParameter("id", entityId)
.getSingleResult();

Hibernate.initialize(entity.persons);

Desta forma, você pode atingir seu objetivo com 2 consultas SQL e evitar um Produto Cartesiano.

Vlad Mihalcea
fonte
Oi Vlad, funciona se eu ligar Hibernate#initialize(entity.getSubSet())se getSubSet retornar Collections.unmodifyableSet(this.subSet). Eu tentei e não funcionou. A coleção subjacente é 'PersistentSet'. Mesma história com a chamada#size()
Vadim Kirilchuk
Mas talvez o problema seja que eu chamo mais tarde contém e meus iguais usam acesso de campo direto e não getters.
Vadim Kirilchuk
Funciona se você seguir as etapas fornecidas em minha resposta.
Vlad Mihalcea
2

Provavelmente não se aproxima de uma prática recomendada, mas geralmente chamo a SIZEna coleção para carregar os filhos na mesma transação, como você sugeriu. É limpo, imune a qualquer mudança na estrutura dos elementos filhos e produz SQL com baixo overhead.

Davek
fonte
0

Tente usar Gson biblioteca para converter objetos em Json

Exemplo com servlets:

  List<Party> parties = bean.getPartiesByIncidentId(incidentId);
        String json = "";
        try {
            json = new Gson().toJson(parties);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write(json);
Mohamed Nagy
fonte
0

se você estiver usando o repositório jpa, defina properties.put ("hibernate.enable_lazy_load_no_trans", true); para jpaPropertymap

userSait
fonte
0

Você pode usar a @NamedEntityGraphanotação para sua entidade para criar uma consulta carregável que define quais coleções você deseja carregar em sua consulta.

A principal vantagem dessa escolha é que o hibernate faz uma única consulta para recuperar a entidade e suas coleções e apenas quando você opta por usar este gráfico, como este:

Configuração de entidade

@Entity
@NamedEntityGraph(name = "graph.myEntity.addresesAndPersons", 
attributeNodes = {
    @NamedAttributeNode(value = "addreses"),
    @NamedAttributeNode(value = "persons"
})

Uso

public MyEntity findNamedGraph(Object id, String namedGraph) {
        EntityGraph<MyEntity> graph = em.getEntityGraph(namedGraph);

        Map<String, Object> properties = new HashMap<>();
        properties.put("javax.persistence.loadgraph", graph);

        return em.find(MyEntity.class, id, properties);
    }
gutors
fonte
0

Há algum tipo de mal-entendido sobre coleções preguiçosas no JPA-Hibernate. Em primeiro lugar, vamos deixar claro que por que tentar ler uma coleção preguiçosa lança exceções e não simplesmente retorna NULL para conversão ou outros casos de uso?.

Isso ocorre porque os campos nulos em bancos de dados, especialmente em colunas unidas, têm significado e não simplesmente um estado não apresentado, como as linguagens de programação. quando você está tentando interpretar uma coleção preguiçosa para um valor nulo, isso significa (no lado do armazenamento de dados) que não há relações entre essas entidades e isso não é verdade. então lançar exceção é algum tipo de prática recomendada e você tem que lidar com isso, não com o Hibernate.

Portanto, como mencionado acima, recomendo:

  1. Desanexe o objeto desejado antes de modificá-lo ou usar uma sessão sem estado para consultar
  2. Manipule campos preguiçosos para os valores desejados (zero, nulo etc.)

também conforme descrito em outras respostas, existem muitas abordagens (busca ansiosa, junção, etc.) ou bibliotecas e métodos para fazer isso, mas você precisa configurar sua visão do que está acontecendo antes de lidar com o problema e resolvê-lo.

Mohsen Msr
fonte