#hibernate #jpa #cdi
#гибернация #jpa #cdi
Вопрос:
Я использую таблицу стратегий наследования для каждого конкретного класса, и сегодня я столкнулся с чем-то очень странным. Я до сих пор не знаю причину этой проблемы, но позвольте мне объяснить, что это такое…
У меня есть следующие классы:
@Entity
@Table(name="...")
@Inheritance(strategy=InheritanceType.JOINED)
public class A implements Serializable{...}
@Entity
@Table(name="...")
@PrimaryKeyJoinColumn(name="...")
public class B extends A{...}
Выглядит довольно просто, и запрос, который я использую для извлечения записей, в основном таков:
FROM A
Это также возвращает экземпляры B, чего я и ожидаю.
Записи загружаются в вспомогательный компонент для представления (компонент имеет область просмотра, контекст сохранения завершен). Представление обращается к компоненту через EL для извлечения записей для таблицы данных:
<h:dataTable value="#{bean.entries}" var="entry">...</h:dataTable>
В таблице данных у меня есть такая командная ссылка:
<h:commandLink value="click" actionListener="#{bean.doSomething}">
<f:setPropertyActionListener value="#{entry}" target="#{bean.selected}" />
</h:commandLink>
Компонент работает с объектами типа A, но CDI-декоратор, который вызывается при вызове ActionListener-Expression, будет выполнять дополнительные действия, если тип выбранной записи является подклассом:
public void doSomething(ActionEvent event){
if(delegate.getSelected() instanceof B){
// special
}else{
delegate.doSomething(event);
}
}
И теперь это усложняется. Если вводится «иногда», но не тогда, когда ожидается. При отладке выяснилось, что объект, возвращаемый с помощью delegate.getSelected()
, имеет тип A_javassisst
, ДАЖЕ если он должен быть экземпляром B. Самое лучшее в этом то, что метод toString() возвращает B @ 123, что позволяет мне сначала поверить, что объект имеет тип B, но это не так…
Теперь мы переходим к моему вопросу… Что, черт возьми, там происходит??? Я уже думал о некоторых проблемах с сериализацией, которые могут возникнуть при сохранении состояния DataTable, но я не уверен (не должен ли datatable извлекать значения, возвращаемые значением-выражением, и использовать их для прохождения, или это может нарушить состояние?).
DataTable — это datatable PrimeFaces, не пробовал использовать JSF datatable, но это не может быть ошибкой primefaces…
Все это в следующей среде:
- БЫЛ 8.0.0.1 (=> OpenWebBeans)
- CODI 1.1.1
- MyFaces 2.1.1
- Переход в режим гибернации 3.6.5
Заранее спасибо за вашу помощь!
Редактировать:
Все мои объекты имеют тип B, это означает, что база данных содержит запись в таблице «b» для каждой записи в таблице «a», но иногда я получаю возвращаемые объекты из JPA / Hibernate, которые не являются экземпляром B!! Пожалуйста, мне нужна помощь в этом, я понятия не имею, почему это может произойти !?!
Редактировать:
Мой диагноз был неправильным, возвращаемые типы являются правильными! У меня действительно есть прокси типа B, а не прокси типа A. Моя проблема заключалась в том, что метод, который был оформлен, был вызван перед установщиком. Это больше связано с JSF, чем с гибернацией!
Я еще не сталкивался с проблемами и подводными камнями, о которых вы сообщали, использование instanceof отлично работает для меня!
Ответ №1:
Симптомы соответствуют ограничению гибернации. Вкратце, прокси с отложенной загрузкой для полиморфных объектов реагируют иначе instanceof
, чем объекты, которые они прокси. Это связано с тем, что экземпляр прокси создается в то время, когда фактический тип объекта еще не известен, и, будучи объектом Java, он не может изменить свой класс времени выполнения после создания.
Hibernate вернет прокси с отложенной загрузкой вместо материализованного объекта, если
- Вы явно запрашиваете прокси с
session.load()
- в качестве замены для объекта, на который ссылается однозначная, лениво выбранная ассоциация из другого загруженного объекта
- прокси-сервер был ранее создан в том же сеансе гибернации с использованием вышеуказанных средств
Случай 2 является наиболее распространенным. Я зашел так далеко, что написал модульный тест, который проверяет мое сопоставление на наличие полиморфных ленивых ненулевых однозначных ассоциаций, чтобы предупредить меня об этой возможности.
Существуют способы использования instanceof
и приведения с прокси-серверами, но они не являются тривиальными. Для приведения прокси-интерфейсы должны быть объявлены для полиморфных объектов, и весь код должен программироваться для этих интерфейсов, а не для классов сущностей. Затем в режиме гибернации прокси-сервер будет реализовывать все интерфейсы, которые могут быть у объекта, позволяя выполнять все приведения. Например instanceof
, вы могли бы объявить:
class A {
boolean isInstanceOf(Class<X extends A> clazz) {
return clazz.isInstance(this);
}
}
а затем напишите
if (entity.isInstanceOf(B.class)) {
B b = (B) entity;
// work with b
}
вместо
if (entity instanceof B) {
...
Комментарии:
1. на мой взгляд, clazz.isInstance имеет тот же эффект, что и использование instanceof, так что это вообще не поможет. Смотрите мое редактирование, ловушка, похоже, больше не является ловушкой?
2.
B.class.isInstance(this)
действительно, имеет тот же эффект,this instanceof B
что и, но вы делаетеentity instanceof B
, и еслиentity
ссылается на прокси с отложенной загрузкой,this != entity
.3. да, вы правы, я так не думал. в любом случае это работает! спасибо за вашу помощь 🙂
Ответ №2:
Таковы проблемы с гибернацией при использовании полиморфизма.
В двух словах, при тралении модели гибернации, где lazy имеет значение true, hibernate не загружает реальный тип — загрузка реального типа потребовала бы попадания в базу данных, и это не было бы ленивым. Если вы хотите, чтобы возвращался реальный тип, вам необходимо настроить классы и установить для lazy значение «прокси». Что это будет делать, так это не увлажнять прокси, пока вы не попросите об этом, только тогда будет запрошен запрос к БД для определения его реального типа — однако такое поведение доступно только при включенном инструментировании, поскольку инструментарий перехватывает вызов.
Отключите lazy везде, где вы можете это увидеть, и посмотрите, изменится ли поведение — оно должно работать с lazy off.
Это одна из ловушек использования hibernate.
Ответ №3:
Я думаю, что clazz.isInstance(this) работает, потому что это экземпляр целевого объекта. В этом разница с instanceof. Примите во внимание, что объект не обрабатывается при этом вызове.
Ответ №4:
Попробуйте отменить блокировку вашего объекта :
/**
*
* @param <T>
* @param entity
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T initializeAndUnproxy(T entity) {
if (entity == null) {
// throw new NullPointerException("Entity passed for initialization is null");
return null;
}
Hibernate.initialize(entity);
if (entity instanceof HibernateProxy) {
entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
}
return entity;
}