Как с нетерпением загружать ленивые поля с JPA 2.0?

У меня есть класс сущности, у которого есть ленивое поле, подобное этому:

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

Поле истории обычно должно загружаться лениво, потому что оно обычно велико. Однако иногда мне нужно загружать его с нетерпением, но я не пишу что-то некрасивое, как movie.getStory(), чтобы заставить загрузку. Для ленивых отношений я знаю, что соединение fetch может заставить загружать загрузку, но это не работает для ленивого поля. Как написать запрос для активной загрузки поля истории?

Ответ 1

Я бы попробовал Hibernate.initialize(movie). Но вызов getter (и добавление комментария, что это заставляет инициализацию) не так уж и плохо.

Ответ 2

В запросе можно использовать ключевые слова fetch all properties:

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

Ответ 3

Чтобы процитировать спецификацию JPA (2.0, 11.1.6):

Стратегия LAZY - это намек на время выполнения провайдера настойчивости, что данные должны быть получены лениво, когда к нему впервые обращаются. Реализации разрешено охотно извлекать данные, для которых Был указан указатель стратегии LAZY.

Hibernate поддерживает только то, что вы пытаетесь использовать, если вы используете его функции улучшения байт-кода. Есть несколько способов сделать это. Сначала нужно использовать инструмент повышения времени сборки. Второй способ - использовать (class-) увеличение времени загрузки. В средах Java EE вы можете включить это в Hibernate JPA, используя параметр "hibernate.ejb.use_class_enhancer" (установите значение true, false - значение по умолчанию). В средах Java SE вам необходимо улучшить классы по мере их загрузки, либо самостоятельно, либо вы можете использовать org.hibernate.bytecode.spi.InstrumentedClassLoader

Ответ 4

Единственное возможное решение:

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

Другим может быть использование @Transactional по методу в ManagedBean или Stateless и попытка доступа к movie.getReferencedEntities(). size() для его загрузки, но он будет генерировать N + 1, т.е. генерирование дополнительных N запросов для каждой взаимосвязи, которая во многих случаях не слишком эффективна.

Ответ 5

Я бы предложил пересечь объекты с помощью отражения Java, вызывая все методы, начинающиеся с "get", и повторять это для всего полученного объекта, если он имеет аннотацию @Entity.

Не самый красивый способ, но он должен быть надежным обходным решением. Что-то вроде этого (еще не проверено):

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

Ответ 6

Если вы не возражаете, чтобы POJO был результатом запроса, вы можете использовать запрос конструктора. Это потребует, чтобы ваш объект имел конструктор со всеми необходимыми параметрами и такой запрос:

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