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.
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 :
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; } }
fuente
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.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(); } }
fuente
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();
fuente
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
fuente
Cuando tenga que buscar varias colecciones, debe:
Hibernate.initialize
para 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.
fuente
Hibernate#initialize(entity.getSubSet())
si getSubSet regresaCollections.unmodifyableSet(this.subSet)
? Lo intenté y no fue así. La colección subyacente es 'PersistentSet'. La misma historia con las llamadas#size()
Probablemente no se esté acercando a una mejor práctica, pero generalmente llamo
SIZE
a 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.fuente
Intente usar la
Gson
biblioteca para convertir objetos a JsonEjemplo 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);
fuente
si usa el repositorio jpa, configure properties.put ("hibernate.enable_lazy_load_no_trans", verdadero); a jpaPropertymap
fuente
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); }
fuente
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:
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.
fuente