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.
Надеюсь, это поможет вам.