Zhao Yi
Zhao Yi

Reputation: 2465

How to eagerly load lazy fields with JPA 2.0?

I have an entity class that has a lazy field like this:

@Entity
public Movie implements Serializable {
    ...
    @Basic(fetch = FetchType.LAZY)
    private String story;
    ...
}

The story field should normally be loaded lazily because it's usually large. However sometimes, I need to load it eagerly, but I don't write something ugly like movie.getStory() to force the loading. For lazy relationship I know a fetch join can force a eager loading, but it doesn't work for lazy field. How do I write a query to eagerly load the story field?

Upvotes: 16

Views: 17298

Answers (6)

Aleksandr Kravets
Aleksandr Kravets

Reputation: 5947

If you don't mind having a POJO as a query result you can use constructor query. This will require your object to have constructor with all needed parameters and a query like this:

select new Movie(m.id, m.story) from Movie m

Upvotes: 0

Michał Ziobro
Michał Ziobro

Reputation: 11752

The one possible solution is:

SELECT movie 
FROM Movie movie LEFT JOIN FETCH movie.referencedEntities
WHERE...

Other could be to use @Transactional on method in ManagedBean or Stateless and try to access movie.getReferencedEntities().size() to load it but it will generate N+1 problem i.e. generating additional N queries for each relationship which isn't too efficient in many cases.

Upvotes: 3

Tom Desair
Tom Desair

Reputation: 176

You can use the fetch all properties keywords in your query:

SELECT movie 
FROM Movie movie FETCH ALL PROPERTIES
WHERE ...

Upvotes: 2

Steve Ebersole
Steve Ebersole

Reputation: 9443

To quote the JPA spec (2.0, 11.1.6):

The LAZY strategy is a hint to the persistence provider runtime that data should be fetched lazily when it is first accessed. The implementation is permitted to eagerly fetch data for which the LAZY strategy hint has been specified.

Hibernate only supports what you are trying if you use its bytecode enhancement features. There are a few ways to do that. First is to use the build-time enhancement tool. The second is to use (class-)load-time enhancement. In Java EE environments you can enable that on Hibernate JPA using the 'hibernate.ejb.use_class_enhancer' setting (set it to true, false is the default). In Java SE environments, you need to enhance the classes as they are loaded, either on your own or you can leverage org.hibernate.bytecode.spi.InstrumentedClassLoader

Upvotes: 1

30thh
30thh

Reputation: 11311

I would suggest to traverse the objects using Java reflection calling all methods starting with "get" and repeat this for all the gotten object, if it has an @Entity annotation.

Not the most beautiful way, but it must be a robust workaround. Something like that (not tested yet):

public static <T> void deepDetach(EntityManager emanager, T entity) {
    IdentityHashMap<Object, Object> detached = new IdentityHashMap<Object, Object>();
    try {
        deepDetach(emanager, entity, detached);
    } catch (IllegalAccessException e) {
        throw new RuntimeException("Error deep detaching entity [" + entity + "].", e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException("Error deep detaching entity [" + entity + "].", e);
    }
}


private static <T> void deepDetach(EntityManager emanager, T entity, IdentityHashMap<Object, Object> detached) throws IllegalAccessException, InvocationTargetException {
    if (entity == null || detached.containsKey(entity)) {
        return;
    }

    Class<?> clazz = entity.getClass();

    Entity entityAnnotation  = clazz.getAnnotation(Entity.class);
    if (entityAnnotation == null) {
        return; // Not an entity. No need to detach.
    }

    emanager.detach(entity);
    detached.put(entity, null); // value doesn't matter. Using a map, because there is no IdentitySet.

    Method[] methods = clazz.getMethods();

    for (Method m : methods) {
        String name = m.getName();
        if (m.getParameterTypes().length == 0) {
            if (name.length() > 3 && name.startsWith("get") && Character.isUpperCase(name.charAt(3))) {
                Object res = m.invoke(entity, new Object[0]);
                deepDetach(emanager, res, detached);
            }
            // It is actually not needed for searching for lazy instances, but it will load
            // this instance, if it was represented by a proxy
            if (name.length() > 2 && name.startsWith("is") && Character.isUpperCase(name.charAt(2))) {
                Object res = m.invoke(entity, new Object[0]);
                deepDetach(emanager, res, detached);
            }
        }
    }
}

Upvotes: -1

Bozho
Bozho

Reputation: 597016

I'd try Hibernate.initialize(movie). But calling the getter (and adding a comment that this forces initialization) is not that wrong.

Upvotes: 3

Related Questions