Решение LazyInitializationException через невежество
здесь бесчисленное множество вопросов, как решить проблему "не удалось инициализировать прокси" с помощью нетерпеливой выборки, сохраняя транзакцию открытой, открывая другую,OpenEntityManagerInViewFilter
и все.
но можно ли просто сказать Hibernate игнорировать проблему и притворяться, что коллекция пуста? В моем случае, не получить его раньше просто означает, что мне все равно.
это на самом деле проблема XY со следующим Y:
у меня такие занятия, как
class Detail {
@ManyToOne(optional=false) Master master;
...
}
class Master {
@OneToMany(mappedBy="master") List<Detail> details;
...
}
и хотите обслуживать два вида запросов: один возвращает один master
со всеми его details
и еще один, возвращающий список master
s без details
. Результат преобразуется в JSON с помощью Gson.
я пробовал session.clear
и session.evict(master)
, но они не касаются прокси, используемого вместо details
. Что работал
master.setDetails(nullOrSomeCollection)
, который чувствует себя довольно суховато. Я бы предпочел "невежество" как это было бы применимо в целом, не зная, какие части того, что проксируется.
написание Gson TypeAdapter
игнорируя экземпляров AbstractPersistentCollection
С initialized=false
может быть, но это будет зависеть от org.hibernate.collection.internal
, что, конечно, нехорошо. Поймать исключение в TypeAdapter
звучит не намного лучше.
обновление после некоторых ответов
моя цель-не "получить данные, загружаемые вместо исключения", но "как сделать null вместо исключения" Я!--19-->
Драгана поднимает допустимый момент, что забывание выборки и возврат неправильных данных будет намного хуже, чем исключение. Но есть простой способ обойти это:
- сделайте это только для коллекций
- никогда не используйте
null
для них - возвращение
null
, а не пустой набор как признак когда есть еще невыбранные данные
таким образом, результат не может быть интерпретирован. Если я когда-нибудь забуду что-то принести, ответ будет содержать null
что является недопустимым.
5 ответов
вы можете использовать спящий режим.isInitialized, который является частью общедоступного API Hibernate.
Так, в TypeAdapter
вы можете добавить что-то вроде этого:
if ((value instanceof Collection) && !Hibernate.isInitialized(value)) {
result = new ArrayList();
}
однако, по моему скромному мнению, ваш подход вообще не тот путь.
что вы можете попробовать это решение, как показано ниже.
создание интерфейса с именем LazyLoader
@FunctionalInterface // Java 8
public interface LazyLoader<T> {
void load(T t);
}
и к вашим услугам
public class Service {
List<Master> getWithDetails(LazyLoader<Master> loader) {
// Code to get masterList from session
for(Master master:masterList) {
loader.load(master);
}
}
}
и вызовите эту услугу, как показано ниже
Service.getWithDetails(new LazyLoader<Master>() {
public void load(Master master) {
for(Detail detail:master.getDetails()) {
detail.getId(); // This will load detail
}
}
});
и в Java 8 вы можете использовать лямбда, поскольку это один абстрактный метод (SAM).
Service.getWithDetails((master) -> {
for(Detail detail:master.getDetails()) {
detail.getId(); // This will load detail
}
});
вы можете использовать решение выше с session.clear
и session.evict(master)
я поднимал аналогичный вопрос в прошлом (почему зависимая коллекция не выселяется, когда родительская сущность), и это привело к ответу, который вы могли бы попробовать для своего случая.
решение для этого заключается в использовании запросов вместо ассоциаций (один ко многим или много ко многим). Даже один из оригинальных авторов Hibernate сказал, что коллекции-это функция и не конечная цель.
в вашем случае вы можете получить лучшую гибкость удаления сопоставления коллекций и просто получить связанные отношения, когда они вам нужны в вашем слое доступа к данным.
вы можете создать прокси Java для каждого объекта, так что каждый метод окружен блоком try/catch, который возвращает null
когда LazyInitializationException
это поймал.
чтобы это работало, все ваши сущности должны были бы реализовать интерфейс, и вам нужно было бы ссылаться на этот интерфейс (вместо класса сущности) во всей вашей программе.
если вы не можете (или просто не хотите) использовать интерфейсы, то вы можете попытаться создать динамический прокси с помощью javassist или cglib, или даже вручную, как описано в в этой статье.
если вы идете по общим прокси Java, вот эскиз:
public static <T> T ignoringLazyInitialization(
final Object entity,
final Class<T> entityInterface) {
return (T) Proxy.newProxyInstance(
entityInterface.getClassLoader(),
new Class[] { entityInterface },
new InvocationHandler() {
@Override
public Object invoke(
Object proxy,
Method method,
Object[] args)
throws Throwable {
try {
return method.invoke(entity, args);
} catch (InvocationTargetException e) {
Throwable cause = e.getTargetException();
if (cause instanceof LazyInitializationException) {
return null;
}
throw cause;
}
}
});
}
Итак, если у вас есть сущность A
следующим образом:
public interface A {
// getters & setters and other methods DEFINITIONS
}
С его реализацией:
public class AImpl implements A {
// getters & setters and other methods IMPLEMENTATIONS
}
затем, предполагая, что у вас есть ссылка на класс сущности( возвращаемый Hibernate), вы можете создать прокси-сервер как следует:
AImpl entityAImpl = ...; // some query, load, etc
A entityA = ignoringLazyInitialization(entityAImpl, A.class);
Примечание 1: вам также понадобятся прокси-коллекции, возвращаемые Hibernate (слева в качестве отрывка для читателя);)
примечание 2: в идеале вы должны делать все это проксирование в DAO или в каком-то фасаде, чтобы все было прозрачно для пользователя сущностей
Примечание 3: это никоим образом не оптимально, так как он создает stacktrace для каждого доступа к неинициализированному поле
примечание 4: это работает, но добавляет сложности; подумайте, действительно ли это необходимо.