Решение LazyInitialization Исключение из-за незнания
Здесь много бесчисленных вопросов, как решить проблему "не удалось инициализировать прокси", пытаясь получить, открыв транзакцию, открыв другую, OpenEntityManagerInViewFilter
и т.д.
Но можно ли просто сказать Hibernate игнорировать проблему и притворяться, что коллекция пуста? В моем случае, не доставляя его раньше, просто означает, что мне все равно.
На самом деле это проблема XY со следующим Y:
У меня есть классы вроде
class Detail {
@ManyToOne(optional=false) Master master;
...
}
class Master {
@OneToMany(mappedBy="master") List<Detail> details;
...
}
и хотите обслуживать два типа запросов: один возвращает один master
со всеми его details
, а другой возвращает список master
без details
. Результат преобразуется в JSON Gson.
Я пробовал session.clear
и session.evict(master)
, но они не касаются прокси-сервера вместо details
. Что работало
master.setDetails(nullOrSomeCollection)
который чувствует себя довольно взломанным. Я бы предпочел "незнание", поскольку это применимо вообще, не зная, какие части проксированных.
Запись Gson TypeAdapter
игнорирование экземпляров AbstractPersistentCollection
с помощью initialized=false
может быть способом, но это будет зависеть от org.hibernate.collection.internal
, что, безусловно, не очень хорошо. Улавливание исключения в TypeAdapter
звучит не намного лучше.
Обновление после некоторых ответов
Моя цель состоит не в том, чтобы "получить загруженные данные вместо исключения", но "как получить null вместо исключения"
I
Драган поднимает действительный момент, когда забывание забрать и вернуть неверные данные будет намного хуже, чем исключение. Но там есть простой способ:
- делать это только для коллекций
- никогда не используйте
null
для них
- return
null
, а не пустая коллекция в качестве индикатора необработанных данных
Таким образом, результат никогда не может быть неправильно интерпретирован. Должен ли я когда-либо забыть что-то получить, ответ будет содержать null
, который недействителен.
Ответы
Ответ 1
Вы можете использовать Hibernate.isInitialized, который является частью общедоступного API Hibernate.
Итак, в TypeAdapter
вы можете добавить что-то вроде этого:
if ((value instanceof Collection) && !Hibernate.isInitialized(value)) {
result = new ArrayList();
}
Однако, по моему скромному мнению, ваш подход в целом - это не путь.
"В моем случае, не выбирая его, просто означает, что мне все равно".
Или это означает, что вы забыли его получить, и теперь вы возвращаете неверные данные (хуже, чем получение исключения, потребитель службы считает, что коллекция пуста, но это не так).
Я бы не хотел предлагать "лучшие" решения (это не тема вопроса, и каждый подход имеет свои преимущества), но способ решения таких проблем в большинстве случаев использования (и это один из общепринятые способы) использует DTO: просто определите DTO, который представляет ответ службы, заполните его в транзакционном контексте (нет LazyInitializationException
там) и передайте его в инфраструктуру, которая преобразует ее в ответ службы (json, xml и т.д.).
Ответ 2
Что вы можете попробовать - это решение, подобное следующему.
Создание интерфейса с именем 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 вы можете использовать Lambda, поскольку это единый абстрактный метод (SAM).
Service.getWithDetails((master) -> {
for(Detail detail:master.getDetails()) {
detail.getId(); // This will load detail
}
});
Вы можете использовать вышеприведенное решение с session.clear
и session.evict(master)
Ответ 3
В прошлом я затронул аналогичный вопрос (почему зависимая коллекция не выдается, когда родительский объект), и он дал ответ, который вы могли бы попробовать для вашего случая.
Ответ 4
Решением для этого является использование запросов вместо ассоциаций (один-ко-многим или многим-ко-многим). Даже один из оригинальных авторов Hibernate сказал, что Коллекции - это функция, а не конечная цель.
В вашем случае вы можете получить лучшую гибкость при удалении сопоставления коллекций и просто получить связанные отношения, когда они вам понадобятся на вашем уровне доступа к данным.
Ответ 5
Вы можете создать прокси-сервер 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 (слева в качестве excersice для читателя);)
ПРИМЕЧАНИЕ 2. В идеале вы должны делать все это проксирование в DAO или на каком-то фасаде, чтобы все было прозрачно для пользователя объектов
ПРИМЕЧАНИЕ 3. Это отнюдь не оптимально, поскольку он создает стек для каждого доступа к неинициализированному полю
ПРИМЕЧАНИЕ 4: Это работает, но добавляет сложности; подумайте, действительно ли это необходимо.