Hibernate JPA, наследование и хранимая процедура, возвращающие несколько наборов результатов

Я пытаюсь использовать несколько наборов результатов из хранимой процедуры с помощью Hibernate 4.3.5.Final (JPA 2.1) - и мне не удалось заставить его работать . Я использую Sql Server 2008.

Сохраненные наборы результатов процедуры имеют разные столбцы с некоторой общностью, но недостаточной для объединения их в один набор результатов. Общность выражается в Java с помощью иерархии наследования. Я использовал стратегию InheritanceType для TABLE_PER_CLASS, хотя на самом деле в наборах результатов хранимых процедур нет явных таблиц. Тем не менее, мне нужно что-то сделать, чтобы Hibernate гидратировал объект класса X1 для одного набора результатов и X2 для другого.

Моя упрощенная иерархия Java выглядит следующим образом:

@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
@DiscriminatorColumn(
   name="clazz_",
   discriminatorType=DiscriminatorType.INTEGER
)
@DiscriminatorValue(value="0")
public class XBase {
   @Column(name = "ProductTypeID")
   protected Integer productTypeId;
}

и

@Entity
@DiscriminatorValue(value="1")
public class X1 extends XBase {
   @Column(name = "UUID")
   protected String uuid;
}

и

@Entity
@DiscriminatorValue(value="2")
public class X2 extends XBase {
   @Column(name = "geo_id")
   private Integer geoId;
}

Используя @NamedStoredProcedureQuery,

@NamedStoredProcedureQuery (
   name = "XInfoSProc",
   resultClasses = {
        com.xyz.search.jpa.XBase.class,
        com.xyz.search.jpa.X1.class,
        com.xyz.search.jpa.X2.class
  },
  procedureName = "spXInfo",
  parameters = { 
     @StoredProcedureParameter(mode = ParameterMode.IN, name = "XMatchID", type = String.class) 
  }
)

Я создаю StoredProcedureQuery и выполняю его,

// Create an EntityManagerFactory for this Persistence Unit
EntityManagerFactory factory = Persistence.createEntityManagerFactory("XPU");
EntityManager em = factory.createEntityManager();
StoredProcedureQuery spq = em.createNamedStoredProcedureQuery("XInfoSProc");
spq.setParameter("XMatchID", "10002916403");
try {
   spq.execute();
} catch(Exception ex) {
   System.err.println("Exception: " + ex.getMessage());
}

Hibernate выдает исключение WrongClassException,

Исключение: org.hibernate.WrongClassException: объект [id = 512565] не относится к указанному подклассу [com.xyz.search.jpa.X2]: загруженный объект имеет неправильный класс класса com.xyz.search.jpa.X1

Глядя на операторы DEBUG, сгенерированные из спящего режима, кажется, что мои аннотации @DiscriminatorValue () не обрабатываются должным образом. Несмотря на то, что я указал @DiscriminatorValue (value = "1") для X1, спящий режим упорно генерирует SQL с 2 для X1 (2 как clazz_ из X1). Это может быть причиной проблемы, а может и нет, я не уверен пока что.

Есть ли способ использовать Hibernate / JPA с сохраненными процессами, возвращающими несколько наборов результатов?

Что я делаю неправильно?

Заранее спасибо!

(Если кому-то нужна дополнительная информация из моего тестового кода, plmk. :)

Приложение (отредактировано):

Следуя совету zxcf, я изменил @NamedStoredProcedureQuery следующим образом:

@NamedStoredProcedureQuery (
   name = "XInfoSProc",
   resultSetMappings = {
      "XInfoSProcMapping1",
      "XInfoSProcMapping2",
      "XInfoSProcMapping3",
      "XInfoSProcMapping7"
   },
   procedureName = "spXInfo",
   parameters = { 
      @StoredProcedureParameter(mode = ParameterMode.IN, name = "SearchID", type = String.class) 
   }
)

и добавил SqlResultSetMapping следующим образом:

@SqlResultSetMappings(
   value = {
      @SqlResultSetMapping (
         name="XInfoSProcMapping1",
         entities= {
            @EntityResult(entityClass=X1.class,
               discriminatorColumn="clazz_",
               fields={
                  @FieldResult(name="id", column="XID"),
                  @FieldResult(name="typeId", column="XTypeID"),
                  @FieldResult(name="productTypeId", column="XProductTypeID"),
                  @FieldResult(name="natsId", column="NatsId")
               }
            )
         }
      ),
      @SqlResultSetMapping (
         name="XInfoSProcMapping2",
         entities= {
            @EntityResult(entityClass=X2.class,
               discriminatorColumn="clazz_",
               fields={
                  @FieldResult(name="id", column="XID"),
                  @FieldResult(name="typeId", column="XTypeID"),
                  @FieldResult(name="productTypeId", column="XProductTypeID"),
                  @FieldResult(name="phoneNumber", column="PhoneNumber")
               }
            )
         }
      ),
      @SqlResultSetMapping (
         name="XInfoSProcMapping3",
         entities= {
            @EntityResult(entityClass=X3.class,
               discriminatorColumn="clazz_",
               fields={
                  @FieldResult(name="id", column="XID"),
                  @FieldResult(name="typeId", column="XTypeID"),
                  @FieldResult(name="productTypeId", column="XProductTypeID")
               }
            )
         }
      ),
      @SqlResultSetMapping (
         name="XInfoSProcMapping7",
         entities= {
            @EntityResult(entityClass=X7.class,
               discriminatorColumn="clazz_",
               fields={
                  @FieldResult(name="id", column="XID"),
                  @FieldResult(name="typeId", column="XTypeID"),
                  @FieldResult(name="productTypeId", column="XProductTypeID"),
                  @FieldResult(name="geoId", column="geo_id")
               }
            )
         }
      )
   }
)

С этой модификацией я получаю очень странное поведение. Обработка каждого набора результатов по очереди с использованием List x = spq.getResultList () показывает, что x на самом деле является Object [], где каждая строка в наборе результатов сопоставлена ​​с каждым классом, то есть строка 1 набора результатов 1 имеет отображение на X1, X2, X3 и X7. Это совсем не все, что я ожидал - я думал, что resultSets будет отображаться один за другим, т.е. первый resultSet будет отображаться в X1, 2nd в X2 и т. Д., Но это не то, что происходит.

Обновление 10.07.2014 -

В XBase.java,

@SqlResultSetMapping (
   name="XInfoSProcMapping",
   entities= {
      @EntityResult(entityClass=XBase.class,
         discriminatorColumn="dc",
         fields={
            @FieldResult(name="id", column="XID"),
            @FieldResult(name="typeId", column="XTypeID"),
            @FieldResult(name="productTypeId", column="XProductTypeID"),
            @FieldResult(name="natsId", column="NatsId"),
            @FieldResult(name="xUUID", column="XUUID"),
            @FieldResult(name="phoneNumber", column="PhoneNumber"),
            @FieldResult(name="xAddress1", column="XAddress1"),
            @FieldResult(name="couponURL", column="CouponURL"),
            @FieldResult(name="geoId", column="geo_id"),
         }
      )
   }
)
@NamedStoredProcedureQuery (
   name = "XInfoSProc",
   resultSetMappings = {
      "XInfoSProcMapping"
   },
   procedureName = "spXInfo",
   parameters = { 
      @StoredProcedureParameter(mode = ParameterMode.IN, name = "XMatchID", type = String.class) 
   }
)
@Entity
@Inheritance(strategy=javax.persistence.InheritanceType.SINGLE_TABLE)
public abstract class XBase {
   @Id protected Long id;
}

В X1.java,

@Entity
@DiscriminatorValue(value="1")
public class X1 extends XBase {
    /* ... */
}

В X2.java,

@Entity
@DiscriminatorValue(value="2")
public class X2 extends XBase {
    /* ... */
}

Первый набор результатов (который имеет '1' as dc для всех строк) обрабатывается правильно, но при попытке обработать второй набор результатов, который имеет '2' as dc для всех строк, я получаю исключение ClassCastException.

java.lang.ClassCastException: com.xyz.search.jpa.X1 нельзя преобразовать в com.xyz.search.jpa.X2

Я пытаюсь обработать объекты, возвращенные из второго getResultList () в X2, но, по-видимому, JPA в спящем режиме увлажняет X1 даже для строк с dc = '2' - по-видимому, не обращая внимания на столбец дискриминатора, чтобы определить, что создавать.

Набор результатов хранимой процедуры 1:

XID XTypeID XProductTypeID  XUUID   NatsID  XPriority   dc
512565  2   2001    AD6AB5A8-3A75-449D-8742-76C2425BA164    1809025090  10  1

Набор результатов хранимой процедуры 2:

XID XTypeID Name    PhoneNumber dc
512565  2   ABC DEF 8152597378  2

Приведенные выше результаты sp являются репрезентативными - есть много других столбцов, которые я вырезал для ясности. Также есть 5 дополнительных наборов результатов, каждый из которых имеет другой набор столбцов и свое значение для dc: 1,2,3,4,5,6,7

Некоторые (возможно, последние) мысли:

Чем больше я вникаю в это, тем яснее становится, что Hibernate 4.3.5 Final не предназначен для адекватной обработки нескольких наборов результатов из одной хранимой процедуры. В общем, нет гарантии, что два набора результатов из данной хранимой процедуры будут иметь что-то общее, возможно, даже не один и тот же первичный ключ. Решение о создании нескольких наборов результатов из хранимой процедуры может быть обусловлено эффективностью - например, одни и те же этапы предварительной обработки (например, создание временной таблицы) могут потребоваться на стороне SQL для создания нескольких разных наборов результатов.

Однако единственное средство в JPA для создания экземпляров разных классов для каждой строки SQL - это поле дискриминатора, а дискриминаторы работают только с наследованием, которое предполагает хотя бы некоторую общность. Если нет общего идентификатора, первичного ключа, тогда иерархия классов Java не может работать.

И даже если можно идентифицировать общее поле @Id, строки из разных наборов результатов, которые имеют одинаковое значение для поля Id, будут гидратированы в существующий объект, даже если остальная часть строки полностью отличается. Hibernate, очевидно, игнорирует поле дискриминатора, если в кеше уже есть объект с этим идентификатором.

Даже подход MappedSuperclass требует общего поля Id. И, кроме того, нет способа указать таблицу (name = "???") для подклассов, потому что набор результатов не является именованной таблицей, на которую может быть сделана ссылка.


person laloumen    schedule 07.07.2014    source источник
comment
Пожалуйста, добавьте определения ваших именованных запросов хранимых процедур.   -  person Maciej Dobrowolski    schedule 07.07.2014
comment
Добавлен @NamedStoredProcedureQuery.   -  person laloumen    schedule 07.07.2014
comment
вы можете вставить одну строку из результата запроса хранимой процедуры?   -  person Maciej Dobrowolski    schedule 10.07.2014
comment
@zxcf Я добавил первую строку из первых двух наборов результатов. Как я сказал при редактировании, всего имеется 7 наборов результатов, так что каждая строка в наборе результатов №1 имеет dc '1', каждая строка в наборе результатов №2 имеет dc '2' и так далее. Класс X1 имеет DiscriminatorValue (значение = 1), X2 имеет DiscriminatorValue (значение = 2) и т. Д. Я ожидаю, что Hibernate тем самым отобразит набор результатов № 1 в X1, набор результатов № 2 в X2 и т. Д. Но это не то, что он происходит. Только экземпляр X1 гидратируется.   -  person laloumen    schedule 10.07.2014
comment
У меня это работает, есть ли у вас @DiscriminatorColumn определение в XBase классе? попробуйте изменить его на String, я только что сделал аналогичный проект здесь, и он работает как шарм. Или, может быть, одна из ваших процедур возвращает в результатах не только один класс, а вы получаете их как список других подклассов? Взгляните сюда: java2s.com/   -  person Maciej Dobrowolski    schedule 11.07.2014
comment
@zxcf В этом руководстве не используется хранимая процедура, которая возвращает несколько наборов результатов. Я знаю, что Hibernate JPA отлично работает для базового случая одной таблицы с дискриминатором, но дискриминатор, похоже, не различает наборы результатов из сохраненной процедуры.   -  person laloumen    schedule 11.07.2014
comment
@zxcf Я копался в коде Hibernate, и мне кажется, что для каждой хранимой процедуры может быть только один PojoInstantiator. Я еще не уверен на 100%, но, если это так, это определенно объяснит результаты, которые я получаю.   -  person laloumen    schedule 11.07.2014


Ответы (1)


Result-classes будет работать, если вы вернете разные объекты в одной строке.

Я бы изменил ваш @NamedStoredProcedureQuery на resultSetMappings

@NamedStoredProcedureQuery (
   name = "XInfoSProc",
   resultSetMappings = {
        "XInfoSProcMapping"
  },
  procedureName = "spXInfo",
  parameters = { 
     @StoredProcedureParameter(mode = ParameterMode.IN, name = "XMatchID", type = String.class) 
  }
)

и добавить определение SqlResultSetMapping

@SqlResultSetMapping(
        name="XInfoSProcMapping",
        entities=
        @EntityResult(
                entityClass=XBase.class,
                discriminatorColumn="clazz_",
                fields={
                        @FieldResult(name="productTypeId", column="ProductTypeID"),
                        @FieldResult(name="uuid", column="UUID"),
                        @FieldResult(name="geoId", column="geo_id")
                }
        )
)

Как видите, я предполагаю, что ваша процедура возвращает по крайней мере четыре столбца clazz_, ProductTypeID, UUID и geo_id.

Обновить

Я думаю, вы меня неправильно поняли. Я до сих пор не знаю, что возвращает ваша хранимая процедура, но довольно редко возвращать несколько экземпляров в одной строке.

Если вы заявляете

resultClasses = {
    com.xyz.search.jpa.XBase.class,
    com.xyz.search.jpa.X1.class,
    com.xyz.search.jpa.X2.class
}

тогда вы говорите JPA, что каждая отдельная строка содержит три экземпляра классов, и вы позволяете ей отображать себя.

Если вы заявляете

resultSetMappings = {
  "XInfoSProcMapping1",
  "XInfoSProcMapping2",
  "XInfoSProcMapping3",
  "XInfoSProcMapping7"
}

Затем вы говорите JPA, что каждая отдельная строка содержит по крайней мере четыре «объекта», которые отображаются этими сопоставлениями.

На мой взгляд, вам следует объявить единственный resultSetMapping, назовем его XInfoSProcMapping. Итак, NamedStoredProcedureQuery должен выглядеть так:

@NamedStoredProcedureQuery (
   name = "XInfoSProc",
   resultSetMappings = {
      "XInfoSProcMapping"
   },
   procedureName = "spXInfo",
   parameters = { 
      @StoredProcedureParameter(mode = ParameterMode.IN, name = "SearchID", type = String.class) 
   }
)

И SqlResultSetMapping должен выглядеть следующим образом:

@SqlResultSetMapping (
    name="XInfoSProcMapping1",
    entities= {
        @EntityResult(entityClass=XBase.class,
           discriminatorColumn="clazz_",
           fields={
              @FieldResult(name="id", column="XID"),
              @FieldResult(name="typeId", column="XTypeID"),
              @FieldResult(name="productTypeId", column="XProductTypeID"),
              @FieldResult(name="natsId", column="NatsId"),
              @FieldResult(name="phoneNumber", column="PhoneNumber"),
              @FieldResult(name="geoId", column="geo_id")

           }
        )
    }
)

Важно то, что список EntityResult->fields должен соответствовать всем столбцам, возвращаемым вашим запросом хранимой процедуры. Наследование и создание конкретного объекта будет выполняться провайдером JPA.

Надеюсь, это поможет тебе.

person Maciej Dobrowolski    schedule 07.07.2014
comment
Я пробую этот подход и имею аналогичную проблему. Не похоже, что @DiscriminatorValue (value = 1) работает. Журналы по-прежнему показывают: select ... from (select ... 0 as clazz_ from XBase union all select ... 1 as clazz_ from X2 union all select ... 2 as clazz_ from X1) ... хотя у меня есть @DiscriminatorValue (value = 1) в общедоступном классе X1 расширяет XBase {...} Я пытался изменить значение на value = 42, но это не меняет запрос. - person laloumen; 07.07.2014
comment
Можете ли вы опубликовать как результат вызова хранимой процедуры, так и новую версию аннотации @NamedStoredProcedureQuery? - person Maciej Dobrowolski; 08.07.2014
comment
Я добавил аннотации SqlResultSetMappings и NamedStoredProcedureQuery и попытался объяснить получаемые результаты. Фактически, каждый набор результатов сопоставляется с каждым entityClass, так что spq.getResultList () имеет правильное количество строк, но каждая строка является Object [], где obj [0] - отображение строки на X1, obj [1 ] - ›X2 и т. Д. Для меня это совершенно неожиданно, так что я явно что-то делаю не так. Спасибо, zxcf, что нашли время помочь с этим !!! - person laloumen; 08.07.2014
comment
Я обновил сообщение, чтобы отразить результаты предложенных вами изменений. Проблема, похоже, в том, что Hibernate неправильно работает со столбцом дискриминатора. Я выполнил хранимую процедуру и увидел, что каждый набор результатов имеет правильное значение dc (имя изменено с clazz_ на dc), но создается неправильный класс. Есть идеи, в чем может быть проблема? Спасибо!!!! - person laloumen; 10.07.2014
comment
Я вошел в код Hibernate в отладчике и изучил EntityPersister в SessionImpl.instantiate (), и, к удивлению, в нем для переменной-члена DiscriminatorColumnName установлено значение DTYPE вместо того, что я указал в аннотации, dc. Я изменил сохраненную процедуру, чтобы изменить столбец dc на DTYPE, но результаты не изменились. - person laloumen; 10.07.2014
comment
См. Новый раздел в исходном сообщении. Некоторые (возможно, последние) мысли: - Буду признателен за ваши комментарии к тому, что я там написал. :) Спасибо! - person laloumen; 11.07.2014