Как решить javax.persistence.EntityNotFoundException с JPA (не используя @NotFound)
Мы используем JPA для загрузки некоторых материалов из базы данных. Некоторые объекты могут иметь необязательные отношения между ними, например.
@Entity
public class First {
....
@OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.DETACH})
@JoinColumns(value = {
JoinColumn(name = "A_ID", referencedColumnName = "A_ID", insertable = false, updatable = false),
JoinColumn(name = "B_ID", referencedColumnName = "B_ID", insertable = false, updatable = false)})
private Second second;
Когда эта ассоциация присутствует в базе данных, все работает нормально. Когда это не так, я получаю javax.persistence.EntityNotFoundException
Я хочу вместо исключения иметь это поле как NULL, если ассоциация отсутствует.
Я пробовал несколько разных вещей, например. используя опцию = true в аннотации отношений (которая по умолчанию является опцией по умолчанию), помещая ее как Nullable и т.д. Кажется, что ничего подобного не делает, кажется, что все эти параметры игнорируются.
Я нашел много ссылок, в которых упоминается эта же проблема (и некоторые вопросы здесь, в stackoverflow), но во всех них предлагается использовать аннотацию @NotFound
из Hibernate. Но мы НЕ хотим иметь никаких зависимостей от Hibernate (мы хотим сохранить все чисто JPA).
Кто-нибудь из вас знает какой-либо другой способ решить эту проблему?
Большое спасибо за вашу помощь!
Ответы
Ответ 1
Ниже приведено альтернативное решение этой проблемы. Мне пришлось строить поверх старой базы данных, где отношения иногда были повреждены. Вот как я решил это, используя только JPA.
@PostLoad
public void postLoad(){
try {
if(getObject() != null && getObject().getId() == 0){
setObject(null);
}
}
catch (EntityNotFoundException e){
setObject(null);
}
}
Ответ 2
Я столкнулся с той же проблемой. Он не всегда воспроизводимый, поэтому я не могу его протестировать, но вот некоторые мысли:
- Экземпляр вашего второго класса удаляется, а экземпляр класса First ничего не знает об этом.
- Вам нужен способ, чтобы экземпляр First know, когда его экземпляр Second был удален.
- Вариант каскада для удаления здесь не помогает.
- Вы можете попытаться использовать двунаправленную связь, если экземпляр First существует внутри экземпляра Second. Это позволяет вам обновлять экземпляр Сначала через экземпляр Second перед удалением второй
- Двунаправленные отношения - это зло. Я бы предположил в вашем случае, что Первый - это владелец Второго. Не разрешайте службе удалять Второй экземпляр напрямую. Пусть сервис, который работает с экземплярами Сначала удалите экземпляр Second. В этом случае вы можете сделать "второе" поле сначала нулевое, чем удаление экземпляра Second via EntityManager.
- Вы можете получить исключение, когда выполняются запросы и кеш второго уровня, и запрос имеет подсказку, которая позволяет кэшировать его результат. я предложит вам получить результат запросов с помощью следующего метода:
private List<?> getQueryResult(final Query query)
{
try
{
return query.getResultList();
}
catch (EntityNotFoundException e)
{
return query.setHint("javax.persistence.cache.storeMode", CacheStoreMode.REFRESH).getResultList();
}
}
- Если вы работаете с объектами через EntityManger, но не через запросы, и вы получаете исключение, поскольку сущность кэшируется, вы можете аннулировать все объекты First в кеше, когда вы удаляете Second.
Я бы хотел обсудить это решение, так как я не могу его протестировать и не могу сделать sute он работает. Если кто-то попытается, пожалуйста, дайте мне знать.
PS: если у кого-то есть unit test для спящего режима, который воспроизводит эту проблему, не могли бы вы сообщить мне об этом. Я хочу продолжить исследование.
Ответ 3
Как насчет добавления теста в соответствующий класс сущностей:
public boolean getHasSecond() {
if (this.Second != null) {
return true;
} else {
return false;
}
}
Таким образом, вы можете проверить, существует ли связь...
Ответ 4
Попытайтесь добавить опцию = true в аннотацию OneToOne.