Hibernate的JPA,继承和存储过程返回多个结果集(Hibernate JPA, inheri

2019-10-20 10:15发布

I am attempting to consume multiple result sets from a stored procedure using Hibernate 4.3.5.Final (JPA 2.1) -- and I have not been able to get it to work. I am using Sql Server 2008.

The stored proc result sets have different columns with some commonality but not enough to combine them into a single result set. The commonality is expressed in Java with an inheritance hierarchy. I've been using the InheritanceType strategy of TABLE_PER_CLASS even though there really aren't explicit tables in the stored procedure result sets. Still, I need to do something to get Hibernate to hydrate an object of class X1 for one result set and X2 for the other.

My simplified Java hierarchy is as follows:

@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;
}

and

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

and

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

Using a @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) 
  }
)

I construct the StoredProcedureQuery and execute it,

// 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 throws a WrongClassException exception,

Exception: org.hibernate.WrongClassException: Object [id=512565] was not of the specified subclass [com.xyz.search.jpa.X2] : loaded object was of wrong class class com.xyz.search.jpa.X1

Looking at the DEBUG statements generated from hibernate, it seems that my @DiscriminatorValue() annotations aren't being properly handled. Even though I specified @DiscriminatorValue(value="1") for X1, hibernate obstinately is generating SQL with 2 for X1 (2 as clazz_ from X1) This may be the cause of the issue, or maybe not, I'm not sure yet.

Is there any way to use Hibernate / JPA with stored procs returning multiple result sets?

What am I doing wrong?

Thanks in advance!

(If anyone needs additional information from my test code, plmk. :)

Addendum (edited) :

Following the advice of zxcf, I modified the @NamedStoredProcedureQuery to be:

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

and added a SqlResultSetMapping as follows:

@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")
               }
            )
         }
      )
   }
)

With this modification I am getting some very strange behavior. Processing each result set in turn using List x = spq.getResultList() shows that x is actually an Object[] where each row in the result set has been mapped to each class - that is, row 1 of result set 1, has a mapping to X1, X2, X3 and X7. This is not all what I expected at all - I thought that resultSets would be mapped one by one, i.e. first resultSet mapped to X1, 2nd to X2, etc. but that's not what is happening.

Update 7/10/2014 --

In 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;
}

In X1.java,

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

In X2.java,

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

The first result set (which has '1' as dc for all rows) is processed correctly but, when attempting to process the second result set, which has '2' as dc for all rows, I am getting a ClassCastException.

java.lang.ClassCastException: com.xyz.search.jpa.X1 cannot be cast to com.xyz.search.jpa.X2

I am attempting to case the objects returned from the second getResultList() to X2 but apparently hibernate JPA is hydrating X1s even for rows which have dc='2' - apparently not paying any attention to the discriminator column to determine what to instantiate.

Stored procedure result set 1:

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

Stored procedure result set 2:

XID XTypeID Name    PhoneNumber dc
512565  2   ABC DEF 8152597378  2

The above sp results are representative - there are lots of other columns that I stripped out for clarity. There are also 5 additional result sets, each of which has a different set of columns and a distinct value for dc: 1,2,3,4,5,6,7

Some (possibly final) thoughts:

The more I dig into this, the more it becomes clear that Hibernate 4.3.5 Final isn't designed to adequately handle multiple result sets from a single stored procedure. In general, there is no guarantee that two result sets from a given stored procedure will have anything in common, maybe not even the same primary key. The decision to generate multiple result sets from a stored procedure might be driven by efficiency - for instance, the same pre-processing steps (e.g., temp table generation) might be needed on the SQL side to generate several disparate result sets.

However, the only facility in JPA for the instantiation of different classes per SQL row is a discriminator field, and discriminators only work with inheritance, which presupposes at least some commonality. If there is no common identifier, primary key, then a Java class hierarchy cannot work.

And, even if a common @Id field can be identified, rows from different result sets which have the same value for the Id field will be hydrated into the existing object, even though the remainder of the row is completely different. Hibernate evidently ignores the discriminator field if there is already an object in cache with that Id.

Even the MappedSuperclass approach requires a common Id field. And, in addition, there is no way to specify a Table(name="???") for subclasses because a result set is not a named table to which reference can be made.

Answer 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_ProductTypeIDUUIDgeo_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列表应该适合你的存储过程查询返回的所有列。 继承和具体的对象instantation将JPA提供商来完成。

希望它可以帮助你。



文章来源: Hibernate JPA, inheritance and Stored Procedure returning multiple result sets