ChatGPT解决这个技术问题 Extra ChatGPT

Hibernate: best practice to pull all lazy collections

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.


n
naXa stands with Ukraine

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

It looks attractive ). As I understand if I'll use @Fetch(FetchMode.SUBSELECT), than I can call Hibernate.initialize only once to pull all collections. Am I right?
And how do you manage when you retrieve a collection of MyEntity?
If you call any method like "size()" on a collection in a transaction, it will also initialize it so your example after your initialization is not the best. This said, "Hibernate.initialize(...)" is semantically better then collection.size(), so you have the best advice.
F
Florian Sager

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

Thank you for this answer. I know it's been a while but I was trying to solve this and it was slow going until I read your code here. I also added ifs to the beginning of the second method 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; } Iterate lists otherwise ignored.
What if SecurityException is thrown at o.getClass().getMethods();?
D
Damian

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

I use session.refresh in an iteration to load lazyCollections. and every time I run my program just for one of my entity I got LazyInitializationException and others collection loaded after calling session.refresh. How could this happen
S
StanislavL

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

FetchMode.EAGER is deprecated. The javadoc recommends to use FetchMode.JOIN, now.
C
Community

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


This is actually an anti-pattern. For more info: vladmihalcea.com/…
V
Vlad Mihalcea

When having to fetch multiple collections, you need to:

JOIN FETCH one collection 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.


Hi Vlad, does it work if I call Hibernate#initialize(entity.getSubSet()) if getSubSet returns Collections.unmodifyableSet(this.subSet). I tried and it didn't. Underlaying collection is 'PersistentSet'. Same story with calling #size()
But maybe the issue is that I later call contains and my equals uses direct field access and not getters..
It works if you follow the steps provided in my answer.
d
davek

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.


u
userSait

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


M
Mohsen Msr

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 :

Detach the desired object before modifying it or using stateless session for querying 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.


K
Khoa Le

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

M
Mohamed Nagy

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

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now