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.
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 :
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; } }
fonte
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.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(); } }
fonte
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();
fonte
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
fonte
Ao buscar várias coleções, você precisa:
Hibernate.initialize
para 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.
fonte
Hibernate#initialize(entity.getSubSet())
se getSubSet retornarCollections.unmodifyableSet(this.subSet)
. Eu tentei e não funcionou. A coleção subjacente é 'PersistentSet'. Mesma história com a chamada#size()
Provavelmente não se aproxima de uma prática recomendada, mas geralmente chamo a
SIZE
na 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.fonte
Tente usar
Gson
biblioteca para converter objetos em JsonExemplo 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);
fonte
se você estiver usando o repositório jpa, defina properties.put ("hibernate.enable_lazy_load_no_trans", true); para jpaPropertymap
fonte
Você pode usar a
@NamedEntityGraph
anotaçã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); }
fonte
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:
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.
fonte