Hibernate: mejor práctica para extraer todas las colecciones perezosas

92

Lo que tengo:

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

El problema es que no puedo extraer la colección perezosa después de cerrar la sesión. Pero tampoco puedo no cerrar una sesión en el método de proceder .

Qué solución (solución gruesa):

a) Antes de cerrar la sesión, fuerce la hibernación para extraer colecciones perezosas

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

....

b) Quizás una forma más elegante es usar la @Fetch(FetchMode.SUBSELECT)anotación

Pregunta:

¿Cuál es una mejor práctica / una forma común / una forma más elegante de hacerlo? Significa convertir mi objeto a JSON.

VB_
fuente

Respuestas:

102

Úselo Hibernate.initialize()dentro @Transactionalpara inicializar objetos perezosos.

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

Ahora, fuera de la transacción, puede obtener objetos perezosos.

entity.getAddresses().size();
entity.getPersons().size();
Prabhakaran Ramaswamy
fuente
1
Parece atractivo). Según tengo entendido, si usaré @Fetch (FetchMode.SUBSELECT), entonces puedo llamar a Hibernate.initialize solo una vez para extraer todas las colecciones. Estoy en lo cierto?
VB_
4
¿Y cómo se las arregla cuando recupera una colección de MyEntity?
Alexis Dufrenoy
1
Si llama a cualquier método como "size ()" en una colección en una transacción, también lo inicializará para que su ejemplo después de su inicialización no sea el mejor. Dicho esto, "Hibernate.initialize (...)" es semánticamente mejor que collection.size (), así que tienes el mejor consejo.
Tristan
7

Puede recorrer los Getters del objeto Hibernate en la misma transacción para asegurarse de que todos los objetos secundarios perezosos se obtengan con entusiasmo con la siguiente clase 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
fuente
Gracias por esta respuesta. Sé que ha pasado un tiempo, pero estaba tratando de resolver esto y fue lento hasta que leí su código aquí. También agregué ifs al comienzo del segundo método initializeObject (object, seenObjects, insidePackageName): 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; } Iterar listas de lo contrario ignoradas.
Chip
¿Qué pasa si SecurityException se lanza a o.getClass (). GetMethods () ;?
Oleksii Kyslytsyn
6

No es la mejor solución, pero esto es lo que obtuve:

1) Anote el captador que desea inicializar con esta anotación:

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

}

2) Use este método (se puede poner en una clase genérica, o puede cambiar T con la clase Object) en un objeto después de leerlo de la base de datos:

    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
fuente
Utilizo session.refresh en una iteración para cargar lazyCollections. y cada vez que ejecuto mi programa solo para una de mis entidades, obtengo LazyInitializationException y otras colecciones cargadas después de llamar a session.refresh. ¿Cómo pudo suceder esto?
saba safavi
5

Coloque el Utils.objectToJson (entidad); llame antes del cierre de la sesión.

O puede intentar configurar el modo de recuperación y jugar con un 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
fuente
FetchMode.EAGER está obsoleto. El javadoc recomienda usar FetchMode.JOIN, ahora.
Alexis Dufrenoy
4

Con Hibernate 4.1.6 se introduce una nueva característica para manejar esos problemas de asociación perezosa. Cuando habilita la propiedad hibernate.enable_lazy_load_no_trans en hibernate.properties o en hibernate.cfg.xml, ya no tendrá LazyInitializationException.

Para obtener más información, consulte: https://stackoverflow.com/a/11913404/286588

Granja
fuente
3
En realidad, esto es un anti-patrón. Para más información: vladmihalcea.com/…
Ph03n1x
3

Cuando tenga que buscar varias colecciones, debe:

  1. ÚNETE A FETCH one collection
  2. Utilice el Hibernate.initializepara las colecciones restantes.

Entonces, en su caso, necesita una primera 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);

De esta manera, puede lograr su objetivo con 2 consultas SQL y evitar un Producto cartesiano.

Vlad Mihalcea
fuente
Hola Vlad, ¿funciona si llamo Hibernate#initialize(entity.getSubSet())si getSubSet regresa Collections.unmodifyableSet(this.subSet)? Lo intenté y no fue así. La colección subyacente es 'PersistentSet'. La misma historia con las llamadas#size()
Vadim Kirilchuk
Pero tal vez el problema es que luego llamo contiene y mis iguales usan acceso de campo directo y no captadores ..
Vadim Kirilchuk
Funciona si sigue los pasos proporcionados en mi respuesta.
Vlad Mihalcea
2

Probablemente no se esté acercando a una mejor práctica, pero generalmente llamo SIZEa la colección para cargar a los niños en la misma transacción, como sugirió. Es limpio, inmune a cualquier cambio en la estructura de los elementos secundarios y produce SQL con poca sobrecarga.

Davek
fuente
0

Intente usar la Gsonbiblioteca para convertir objetos a Json

Ejemplo con 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
fuente
0

si usa el repositorio jpa, configure properties.put ("hibernate.enable_lazy_load_no_trans", verdadero); a jpaPropertymap

userSait
fuente
0

Puedes usar el @NamedEntityGraph anotación a su entidad para crear una consulta cargable que establezca qué colecciones desea cargar en su consulta.

La principal ventaja de esta elección es que hibernate realiza una sola consulta para recuperar la entidad y sus colecciones y solo cuando elige usar este gráfico, como este:

Configuración de la entidad

@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
fuente
0

Existe algún tipo de malentendido sobre las colecciones perezosas en JPA-Hibernate. En primer lugar, aclaremos que ¿por qué intentar leer una colección perezosa arroja excepciones y no simplemente devuelve NULL para convertir o más casos de uso?.

Esto se debe a que los campos nulos en las bases de datos, especialmente en las columnas unidas, tienen un significado y no simplemente un estado no presentado, como los lenguajes de programación. cuando intenta interpretar una colección perezosa en un valor nulo, significa (en el lado del almacén de datos) que no hay relaciones entre estas entidades y no es cierto. por lo que lanzar una excepción es una especie de mejor práctica y tienes que lidiar con eso, no con Hibernate.

Entonces, como se mencionó anteriormente, recomiendo:

  1. Desconecte el objeto deseado antes de modificarlo o usar una sesión sin estado para realizar consultas
  2. Manipule los campos diferidos a los valores deseados (cero, nulo, etc.)

También, como se describe en otras respuestas, hay muchos enfoques (búsqueda ansiosa, unión, etc.) o bibliotecas y métodos para hacer eso, pero debe configurar su vista de lo que está sucediendo antes de abordar el problema y resolverlo.

Mohsen Sra.
fuente