Hibernate: best practice to pull all lazy collections

JavaHibernateLazy Loading

Java Problem Overview


What I have:

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

What a problem:

The problem is that I can't pull lazy collection after session has been closed. But I also can't not close a session in proceed method.

What a solution (coarse solution):

a) Before session is closed, force hibernate to pull lazy collections

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

....

b) Maybe more ellegant way is to use @Fetch(FetchMode.SUBSELECT) annotation

Question:

What is a best practice/common way/more ellegant way to do it? Means convert my object to JSON.

Java Solutions


Solution 1 - Java

Use Hibernate.initialize() within @Transactional to initialize lazy objects.

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

Now out side of the Transaction you are able to get lazy objects.

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

Solution 2 - Java

You can traverse over the Getters of the Hibernate object in the same transaction to assure all lazy child objects are fetched eagerly with the following generic helper class:

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

Solution 3 - Java

Not the best solution, but here is what I got:

  1. Annotate getter you want to initialize with this annotation:

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

    }

  2. Use this method (can be put in a generic class, or you can change T with Object class) on a object after you read it from database:

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

    }

Solution 4 - Java

Place the Utils.objectToJson(entity); call before session closing.

Or you can try to set fetch mode and play with code like this

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

Solution 5 - Java

With Hibernate 4.1.6 a new feature is introduced to handle those lazy association problems. When you enable hibernate.enable_lazy_load_no_trans property in hibernate.properties or in hibernate.cfg.xml, you will have no LazyInitializationException any more.

For More refer : https://stackoverflow.com/a/11913404/286588

Solution 6 - Java

When having to fetch multiple collections, you need to:

  1. JOIN FETCH one collection
  2. Use the Hibernate.initialize for the remaining collections.

So, in your case, you need a first JPQL query like this one:

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

This way, you can achieve your goal with 2 SQL queries and avoid a Cartesian Product.

Solution 7 - Java

It's probably not anywhere approaching a best practice, but I usually call a SIZE on the collection to load the children in the same transaction, like you have suggested. It's clean, immune to any changes in the structure of the child elements, and yields SQL with low overhead.

Solution 8 - Java

if you using jpa repository, set properties.put("hibernate.enable_lazy_load_no_trans",true); to jpaPropertymap

Solution 9 - Java

You can use the @NamedEntityGraph annotation to your entity to create a loadable query that set which collections you want to load on your query.

The main advantage of this approach is that hibernate makes one single query to retrieve the entity and its collections only when you choose to use this graph, like this:

Entity configuration

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

Usage

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

Solution 10 - Java

There are some kind of misunderstanding about lazy collections in JPA-Hibernate. First of all let's clear that why trying to read a lazy collection throws exceptions and not just simply returns NULL for converting or further use cases?.

That's because Null fields in Databases especially in joined columns have meaning and not simply not-presented state, like programming languages. when you're trying to interpret a lazy collection to Null value it means (on Datastore-side) there is no relations between these entities and it's not true. so throwing exception is some kind of best-practice and you have to deal with that not the Hibernate.

So as mentioned above I recommend to :

  1. Detach the desired object before modifying it or using stateless session for querying
  2. Manipulate lazy fields to desired values (zero,null,etc.)

also as described in other answers there are plenty of approaches(eager fetch, joining etc.) or libraries and methods for doing that, but you have to setting up your view of what's happening before dealing with the problem and solving it.

Solution 11 - Java

Try use Gson library to convert objects to Json

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionVB_View Question on Stackoverflow
Solution 1 - JavaPrabhakaran RamaswamyView Answer on Stackoverflow
Solution 2 - JavaFlorian SagerView Answer on Stackoverflow
Solution 3 - JavaDamianView Answer on Stackoverflow
Solution 4 - JavaStanislavLView Answer on Stackoverflow
Solution 5 - JavaFarmView Answer on Stackoverflow
Solution 6 - JavaVlad MihalceaView Answer on Stackoverflow
Solution 7 - JavadavekView Answer on Stackoverflow
Solution 8 - JavauserSaitView Answer on Stackoverflow
Solution 9 - JavagutorsView Answer on Stackoverflow
Solution 10 - JavaMohsen MsrView Answer on Stackoverflow
Solution 11 - JavaMohamed NagyView Answer on Stackoverflow