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, сгенерированные из hibernate, кажется, что мои аннотации @DiscriminatorValue() не обрабатываются должным образом. Даже если я указал @DiscriminatorValue (значение ="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. Это не все, чего я ожидал - я думал, что наборы результатов будут отображаться один за другим, то есть первый набор результатов сопоставляется с X1, второй - с X2 и т. Д., Но это не то, что происходит.

Обновление 10.10.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 не может работать.

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

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

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.

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

Другие вопросы по тегам