Проблема с наследованием прокси-сервера в режиме гибернации

#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 вернет прокси с отложенной загрузкой вместо материализованного объекта, если

  1. Вы явно запрашиваете прокси с session.load()
  2. в качестве замены для объекта, на который ссылается однозначная, лениво выбранная ассоциация из другого загруженного объекта
  3. прокси-сервер был ранее создан в том же сеансе гибернации с использованием вышеуказанных средств

Случай 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;
    }