带有自联接表的SqlResultSetMapping

时间:2010-07-23 01:49:49

标签: jpa entity orm

我有一个自我联接的查询,看起来像这样,

从表t1中选择t1。,t2。 左外连接表t2在t2.LFT< t1.LFT 和t2.RGT> t1.RGT AND t2.REG_CODE_PAR ='ALL' AND t1.STATUS_CODE ='A' AND t2.STATUS_CODE ='A'

我正在使用@NamedNativeQuery和结果集映射来获得结果。

@NamedNativeQuery(
    name="findTree",
    query="..... the query above", 
    resultSetMapping = "regionT")

使用以下结果集映射

@SqlResultSetMapping(name = "regionT" , entities ={
    @EntityResult( 
        entityClass = Tree.class
        fields = {
            @FieldResult(name = "regCode", column = "REG_CODE")
            @FieldResult(name = "rgt", column = "RGT"),
            @FieldResult(name = "lft", column = "LFT"),
            @FieldResult(name = "name", column = "NAME"),
            @FieldResult(name = "regCodePar", column = "REG_CODE_PAR"),
            @FieldResult(name = "statusCode", column = "STATUS_CODE")
        }
    ),
    @EntityResult(
        entityClass = TreeSelf.class
        fields = {
            @FieldResult(name = "regCode1", column = "REG_CODE")
            @FieldResult(name = "rgt1", column = "RGT"),
            @FieldResult(name = "lft1", column = "LFT"),
            @FieldResult(name = "name1", column = "NAME"),
            @FieldResult(name = "regCodePar1", column = "REG_CODE_PAR"),
            @FieldResult(name = "statusCode1", column = "STATUS_CODE")
        }
    )
})

实体类包含这样的内容。

@NamedNativeQuery(...)
@SqlResultSetMapping(...)
@Entity
@Table(name = "table")
public class Tree implements Serializable {

    @Id
    @Column(name = "REG_CODE")
    private String regCode;  ... ..getters and setters...}

当我使用em.createQuery(“findTree”)运行查询时,我在两者中得到完全相同的对象

返回的对象数组的第1个和第2个元素。 即使我创建了一个名为TreeSelf的类,它与Tree相同并将其用作第二个

EntityResult而不是使用相同的entityClass有2个EntityResults,我得到相同的

结果。

有人可以指出配置有什么问题吗?

1 个答案:

答案 0 :(得分:1)

让我们看看我是否理解你的问题。您希望从每个本机查询结果行捕获两个Tree实体。第一个实体应该由t1列组成。第二个实体应该由t2列组成。与预期相反,您实际上会收到由t1组成的两个实例。没有来自t2的实例。您在调试时为Tree调用TreeSelf创建了一个doppelganger实体,但TreeSelf最终是不必要的,您想要摆脱它。如果有任何错误,请阻止我。

我认为问题是由于列名不明确。原生查询中的每个列名称出现两次,一次来自t1,一次来自t2。结果映射器似乎是任意选择两个Tree实体的每个模糊列名称的第一次出现。我很惊讶这一切都很有效。我原以为SQLException会抱怨列引用歧义。

另外,您确定要进行左外连接吗?如果找不到t1行的匹配怎么办?它将与t2列中的所有NULL配对。然后你有一个空值Tree实体。我认为。在这种情况下,我甚至不知道结果映射器会做什么。也许你想要一个内部联接?

考虑将此本机查询转换为JPQL查询。 (JPA Criteria API也是如此,但我发现它的例子比较麻烦。)这是本机查询的JPQL版本:

SELECT t1, t2 
FROM Tree t1, Tree t2
WHERE t2.lft < t1.lft AND t2.rgt > t1.rgt AND t2.regCodePar = 'ALL' AND
      t1.statusCode = 'A' AND t2.statusCode = 'A'

N.B。:这会将连接语义更改为内部而不是左外部。

以下是可以运行此查询的代码草图:

EntityManager em = ... // EntityManager by injection, EntityManagerFactory, etc.
String jpql      = ... // Like example above
TypedQuery<Object[]> q = em.createQuery(jpql, Object[].class);

for (Object[] oa : q.getResultList()) {
   Tree t1 = (Tree)oa[0];
   Tree t2 = (Tree)oa[1];
}

如果您因任何原因而无法使用本机查询,请按照以下方法解决列名歧义问题。不是像select t1.*, t2.*那样启动原生查询,而是使用AS为每个列添加别名。 SELECT子句类似于:

SELECT t1.REG_CODE AS t1_REG_CODE, t1.RGT AS t1_RGT, (... rest of t1 cols ...), 
       t2.REG_CODE AS t2_REG_CODE, t2.RGT AS t2_RGT, (... rest of t2 cols ...)

每个column中的FieldResult属性必须相应更改。因此,第一个column下的EntityResult属性应始于t1_,而第二个属性应始于t2_

如果你能找到办法的话,我谦虚地建议你删除原生查询和sql结果映射器,并使用JPA查询语言或Criteria API。

更新:正如您的评论中所确认的,对您的问题的有用答案必须保留左(外)连接语义。不幸的是,JPQL和Criteria API不支持复杂的左连接条件。没有办法使用明确的ON条件限定JPQL左连接。

据我所知,在规范下进行左外连接的唯一方法是遍历实体关系。然后,JPA实现生成一个ON条件来测试身份相等性。相关的spec位是4.4.5“Joins”和4.4.5.2“Left Outer Joins”。

要满足此约束,您希望左连接到其最终父级的每个Tree必须有一个存储最终父级ID的附加列。您可以通过各种方式(视图?)来欺骗这种约束。但是阻力最小的路径似乎是修改本机查询以使用别名参数,删除TreeSelf,并相应地更新结果映射器。尽管如此,Cleverer解决方案仍然受到欢迎......