【问题标题】:JPA: select in polymorphic entities with JPQL, eclipselink and joined inheritance using multiple downcastJPA:在多态实体中选择 JPQL、eclipselink 和使用多重向下转换的连接继承
【发布时间】:2020-07-31 19:39:30
【问题描述】:

我有一个有趣的练习,尝试在使用 eclipselink 2.7.6 时使用单个 JPQL 查询选择多个派生实体。

多态性是使用联合继承来实现的。实体图和java类如下:

+--------------+
|  MainEntity  |
+--------------+                        +--------------+
|              | --- myRef:OneToOne --- |  Referenced  |
+--------------+                        +--------------+
                                        |  r: string   |
                                        +--------------+
                                               ^
                                               |
                                   +-----------+-----------+
                                   |                       |
                            +--------------+        +--------------+
                            |  Derived1    |        |  Derived2    |
                            +--------------+        +--------------+
                            |  d1: string  |        |  d2: string  |
                            +--------------+        +--------------+

@Entity                          
@Table(name="MAIN_ENTITY")
public class MainEntity
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MAIN_ENTITY_ID")
    public Integer mainEntityId;

    @OneToOne(optional = true)
    @JoinColumn(name = "MY_REF", referencedColumnName = "REFERENCED_ID")
    public Referenced myRef;
}

@Entity
@Table(name="REFERENCED")
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="REFERENCED_TYPE",discriminatorType=DiscriminatorType.STRING)
public abstract class Referenced
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "REFERENCED_ID")
    public Integer referencedId;

    @Column(columnDefinition = "TEXT", name = "R")
    public String r;
}

@Entity
@Table(name="Derived1")
@DiscriminatorValue("DERIVED_1")
public class Derived1 extends Referenced
{
    @Column(columnDefinition = "TEXT", name = "D1")
    public String d1;
}

@Entity
@Table(name="Derived2")
@DiscriminatorValue("DERIVED_2")
public class Derived2 extends Referenced
{
    @Column(columnDefinition = "TEXT", name = "D2")
    public String d2;
}

我的目标是有一个单一的查询,这会导致一个表的左侧出现公共列(引用实体的那些),以及右侧出现的派生实体的不同列侧在一个表中。

如果我这样初始化数据:

Derived1 d1 = new Derived1();
d1.r = "R set from Derived1";
d1.d1 = "D1 set from Derived1";
MainEntity me1 = new MainEntity();
me1.myRef = d1;

Derived2 d2 = new Derived2();
d2.r = "R set from Derived2";
d2.d2 = "D1 set from Derived2";
MainEntity me2 = new MainEntity();
me2.myRef = d2;

em.getTransaction().begin();
em.persist(d1);
em.persist(me1);
em.persist(d2);
em.persist(me2);
em.getTransaction().commit();

使用 SQL 我可以使用LEFT JOIN 运算符检索我想要的表:

SELECT 
    m.MAIN_ENTITY_ID,
    r.REFERENCED_ID,
    r.R,
    d1.D1,
    d2.D2
FROM 
    REFERENCED r 
INNER JOIN
    MAIN_ENTITY m on m.MY_REF = r.REFERENCED_ID
LEFT JOIN 
    DERIVED1 d1 ON r.REFERENCED_ID = d1.REFERENCED_ID 
LEFT JOIN 
    DERIVED2 d2 ON r.REFERENCED_ID = d2.REFERENCED_ID

结果:

MAIN_ENTITY_ID REFERENCED_ID R                   D1                   D2                   
-------------- ------------- ------------------- -------------------- -------------------- 
2              1             R set from Derived1 D1 set from Derived1 [null]               
1              2             R set from Derived2 [null]               D1 set from Derived2 

但是,到目前为止,我在使用 JPQL 做同样的事情时遇到了困难。我尝试使用 TREAT 和 (LEFT) JOIN JPQL 运算符的任意组合,但我一点运气都没有。生成的 SQL 连接会强制 d1 和 d2 的 ID 相等(自然会导致没有结果),或者我得到的结果太多,所有这些都是我想要的目标结果的排列。

我可以使用 JPQL 结合 TREAT 和 UNION 运算符来重现 SQL 结果,如下所示:

SELECT 
    m.mainEntityId,
    m.myRef.referencedId,
    m.myRef.r,
    TREAT(m.myRef AS Derived1).d1,
    null as d2
FROM 
    MainEntity m
UNION
SELECT 
    m.mainEntityId,
    m.myRef.referencedId,
    m.myRef.r,
    null as d1,
    TREAT(m.myRef AS Derived2).d2
FROM 
    MainEntity m

结果:

mainEntityId referencedId r                   d1                   d2                   
------------ ------------ ------------------- -------------------- ------------------
2            1            R set from Derived1 D1 set from Derived1 null               
1            2            R set from Derived2 null               D1 set from Derived2 

但是,使用相应的null 选择多次重复查询似乎效率不高且容易出错,因为我不得不为每个子类型重复整个结构。尤其是对于更规范化的数据模型,这种方法似乎过于违反直觉。

显然,我正在尝试将 SQL 范式强加于 JPQL 范式,虽然取得了一些成功,但总体信息是我做错了什么。所以我的问题是,有没有更好的方法来使用 JPQL 来实现这一点?如果没有,在这种情况下你们在做什么?

提前致谢!

【问题讨论】:

    标签: java jpa eclipselink jpql downcast


    【解决方案1】:

    您正在尝试的可以通过使用以下 JPQL 查询和 JOIN-s 来实现:

        SELECT
            m.mainEntityId,
            r.referencedId,
            r.r,
            d1.d1,
            d2.d2
        FROM
            MainEntity m
            LEFT JOIN m.myRef r
            LEFT JOIN TREAT(m.myRef AS Derived1) d1
            LEFT JOIN TREAT(m.myRef AS Derived2) d2
    

    查询返回与您的 SQL 示例中相同的两行。 我必须遵循这些规则才能通过 JPQL 获得正确的结果:

    1. 不要在 SELECT 或 FROM 子句中使用多个间接寻址(SELECT m.myRef.r 之类的内容需要分解为 JOIN m.myRef rSELECT r.r)。

    2. 连接表时使用属性而不是实体名称。正确:FROM MainEntity m LEFT JOIN m.myRef r,错误:FROM MainEntity m LEFT JOIN Reference r。说明:必须指定您要加入的确切属性,以便 JPA 知道要生成什么 ON 条件。如果您在 MainEntity 和 Reference 之间有 1 个以上的关系,那么除非您指定,否则 JPA 不会知道您究竟加入了哪一列。

    3. 使用 LEFT JOIN 可以按预期工作。然而,使用 INNER JOIN 会使 Eclipselink 在 SQL 中生成一些奇怪的东西(它将, Reference t2 附加到 JOIN 子句,结果错误地充满了意外的排列)。我无法解释为什么会发生这种情况。在我看来,INNER JOIN 在语义上是正确的,这在我看来就像一个 Eclipselink 错误。也许你可以打开一个单独的问题并询问它。

    【讨论】:

    • 这行得通,谢谢。您对 JOIN 与 LEFT JOIN 的看法也是正确的。从语义上讲,应该使用内连接,但结果完全相反。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-29
    • 2012-11-17
    • 1970-01-01
    • 1970-01-01
    • 2016-07-05
    相关资源
    最近更新 更多