web-dev-qa-db-fra.com

Hibernate: la meilleure pratique pour extraire toutes les collections paresseuses

Ce que j'ai:

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

Quel problème:

Le problème est que je ne peux pas extraire une collection paresseuse après la fermeture de la session. Mais je ne peux pas non plus ne pas fermer une session avec la méthode procéder

Quelle solution (solution grossière):

a) Avant la fermeture de la session, forcez l'hibernation à retirer les collections paresseuses.

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

....

b) Peut-être qu'une manière plus autonome consiste à utiliser l'annotation @Fetch(FetchMode.SUBSELECT)

Question:

Qu'est-ce qu'une meilleure pratique/méthode commune/méthode plus transparente? Signifie convertir mon objet en JSON.

69
VB_

Utilisez Hibernate.initialize() dans @Transactional pour initialiser les objets paresseux. 

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

Maintenant, en dehors de la transaction, vous pouvez obtenir des objets paresseux. 

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

Vous pouvez parcourir les Getters de l'objet Hibernate dans la même transaction pour vous assurer que tous les objets enfants paresseux sont extraits avec la classe generic helper suivante:

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

Ce n'est pas la meilleure solution, mais voici ce que j'ai obtenu:

1) Annotez le getter que vous souhaitez initialiser avec cette annotation: 

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

}

2) Utilisez cette méthode (peut être placée dans une classe générique, ou vous pouvez changer la classe T avec Object) sur un objet après l'avoir lu dans la base de données:

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

Placez le Utils.objectToJson (entité); appelez avant la fermeture de la session.

Ou vous pouvez essayer de définir le mode de récupération et de jouer avec un code comme celui-ci

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();
4
StanislavL

Avec Hibernate 4.1.6, une nouvelle fonctionnalité est introduite pour traiter ces problèmes d'association paresseux. Lorsque vous activez la propriété hibernate.enable_lazy_load_no_trans dans hibernate.properties ou hibernate.cfg.xml, vous ne rencontrerez plus d'exception LazyInitializationException. 

Pour plus, consultez: https://stackoverflow.com/a/11913404/286588

3
Farm

Il n’est probablement nulle part ailleurs qu’une pratique exemplaire, mais j’appelle généralement un SIZE sur la collection pour charger les enfants dans la même transaction, comme vous l’avez suggéré. Il est propre, à l’abri de tout changement dans la structure des éléments enfants, et génère du SQL avec une surcharge minime.

2
davek

Lorsque vous devez extraire plusieurs collections, vous devez:

  1. REJOIGNEZ FETCH one collection
  2. Utilisez le Hibernate.initialize pour les collections restantes.

Donc, dans votre cas, vous avez besoin d’une première requête JPQL comme celle-ci:

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

Ainsi, vous pouvez atteindre votre objectif avec 2 requêtes SQL et éviter un produit cartésien.

0
Vlad Mihalcea

Essayez d’utiliser la bibliothèque Gson pour convertir des objets en Json

Exemple avec des 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);
0
Mohamed Nagy

si vous utilisez le référentiel jpa, set properties.put ("hibernate.enable_lazy_load_no_trans", true); à jpaPropertymap

0
userSait