Ответ 1
Я бы попробовал Hibernate.initialize(movie)
. Но вызов getter (и добавление комментария, что это заставляет инициализацию) не так уж и плохо.
У меня есть класс сущности, у которого есть ленивое поле, подобное этому:
@Entity
public Movie implements Serializable {
...
@Basic(fetch = FetchType.LAZY)
private String story;
...
}
Поле истории обычно должно загружаться лениво, потому что оно обычно велико. Однако иногда мне нужно загружать его с нетерпением, но я не пишу что-то некрасивое, как movie.getStory(), чтобы заставить загрузку. Для ленивых отношений я знаю, что соединение fetch может заставить загружать загрузку, но это не работает для ленивого поля. Как написать запрос для активной загрузки поля истории?
Я бы попробовал Hibernate.initialize(movie)
. Но вызов getter (и добавление комментария, что это заставляет инициализацию) не так уж и плохо.
В запросе можно использовать ключевые слова fetch all properties
:
SELECT movie
FROM Movie movie FETCH ALL PROPERTIES
WHERE ...
Чтобы процитировать спецификацию 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
Единственное возможное решение:
SELECT movie
FROM Movie movie LEFT JOIN FETCH movie.referencedEntities
WHERE...
Другим может быть использование @Transactional по методу в ManagedBean или Stateless и попытка доступа к movie.getReferencedEntities(). size() для его загрузки, но он будет генерировать N + 1, т.е. генерирование дополнительных N запросов для каждой взаимосвязи, которая во многих случаях не слишком эффективна.
Я бы предложил пересечь объекты с помощью отражения 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);
}
}
}
}
Если вы не возражаете, чтобы POJO был результатом запроса, вы можете использовать запрос конструктора. Это потребует, чтобы ваш объект имел конструктор со всеми необходимыми параметрами и такой запрос:
select new Movie(m.id, m.story) from Movie m