JPA2 EclipseLink Criteria 通过 InheritanceType.JOINED 查询子类

JPA2 EclipseLink Criteria query subclasses via InheritanceType.JOINED

问题简介:

我正在尝试查询我已将模型映射为超类-子类一对一模型的数据库。就是说,"Account" 是超类,它由以下子类之一扩展,并且只能是一种子类类型,不能是两种。

超类包含所有 "Accounts",子类是 "User" 和 "Contact" 类型。我需要从超类中查询,"Account" 因为我必须动态检查可能存在于任一子类中的一对一关系出现。

假设这是我的超类实体,"Account":

@Entity
@Table(name="tbl_account")
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name="type", discriminatorType = DiscriminatorType.STRING)
@NamedQuery(name="Account.findAll", query="SELECT a FROM Account a")
public class Account implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @SequenceGenerator(name="TBL_ACCOUNT_ACCID_GENERATOR", sequenceName="tbl_account_acc_id_seq", allocationSize=1)
    @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TBL_ACCOUNT_ACCID_GENERATOR")
    @Column(name="acc_id")
    private Integer accId;

    //bi-directional one-to-one association to Contact
    @OneToOne(mappedBy="tblAccount")
    private Contact tblContact;

    //bi-directional one-to-one association to User
    @OneToOne(mappedBy="tblAccount")
    private User tblUser;
    ..... /* other fields */ .....

这里是 "User" 和 "Contact" 的子类,它们继承自 "Account",如图所示:

@Entity
@Table(name="tbl_user")
@DiscriminatorValue("u")
@NamedQuery(name="User.findAll", query="SELECT u FROM User u")
public class User extends Account implements Serializable {
    private static final long serialVersionUID = 1L;

    //bi-directional one-to-one association to Account
    @OneToOne
    @PrimaryKeyJoinColumn(name="acc_id")
    private Account tblAccount;
    ..... /* other fields */ .....

    .................

@Entity
@Table(name="tbl_contact")
@DiscriminatorValue("c")
@NamedQuery(name="Contact.findAll", query="SELECT c FROM Contact c")
public class Contact extends Account implements Serializable {
    private static final long serialVersionUID = 1L;

    //bi-directional one-to-one association to Account
    @OneToOne
    @PrimaryKeyJoinColumn(name="acc_id")
    private Account tblAccount;
    ..... /* other fields */ .....

想要的结果:

我想要 Multiselect(通过 CriteriaBuilder)"Account"、"User" 和 "Contact" 详细信息,以便它匹配工作 SQL 示例如下:

SELECT  tc."acc_id",
        tc."name" AS "acc_name"
FROM    "tbl_contact" AS tc
        INNER JOIN
            "tbl_account" AS ta
            ON tc."acc_id" = ta."acc_id"
UNION
SELECT  tu."acc_id",
        CONCAT(tu."forename", ' ', tu."surname") AS "acc_name"
FROM    "tbl_user" AS tu
        INNER JOIN
            "tbl_account" AS ta
            ON tu."acc_id" = ta."acc_id"
ORDER BY "acc_id" ASC;

也就是说,我 select "User" 和 "Contact" 并将用户的 CONCATENATED 名称与匹配来自 "Account" 的 ID 的联系人(通过 UNION)组合在一起。这 SQL returns ALL 帐户与每个相应的名称。 事后看来,我会替换这两行:

ON tu."acc_id" = ta."acc_id" (AND) ON tc."acc_id" = ta."acc_id"

// insert specific account ID here to look for which subclass a particular account belongs to
ON tu."acc_id" = 120 (OR) ON tc."acc_id" = 120 (respectively)

尝试:

下面是我使用 JUnit 测试对 CriteriaAPI 进行的尝试。我使用 "Account" 作为根并将子类声明为 Join 对象,然后使用元模型(从而确保我使用正确的属性)在我的查询中提取相应的字段。

(我意识到返回一个 Object[] 列表会抛出一个 java 未经检查的安全警告。这对于 JUnit 测试来说纯粹是临时的):

@Test
public void testJoinAccounts() {
    CriteriaBuilder cBuilder = em.getCriteriaBuilder();
    CriteriaQuery<Object[]> cQuery = cBuilder.createQuery(Object[].class);
    Root<Account> accountRoot = cQuery.from(Account.class);
    Join<Account, User> joinUser = accountRoot.join(Account_.tblUser);
    Join<Account, Contact> joinContact = accountRoot.join(Account_.tblContact);

    Expression<String> concatUserName = cBuilder.concat(joinUser.<String>get(User_.forename), " ");
    concatUserName = cBuilder.concat(concatUserName, joinUser.<String>get(User_.surname));

    Predicate tblUser = cBuilder.equal(joinUser.get(User_.accId), accountRoot.get(Account_.accId));
    Predicate tblContact = cBuilder.equal(joinContact.get(Contact_.accId), accountRoot.get(Account_.accId));

    cQuery.multiselect(accountRoot.get(Account_.accId), concatUserName, joinContact.get(Contact_.name))
    .where(cBuilder.or(cBuilder.equal(joinUser.get(User_.accId), accountRoot.get(Account_.accId)), (cBuilder.equal(joinContact.get(Contact_.accId), accountRoot.get(Account_.accId)))));

    Query qry = em.createQuery(cQuery);
    List<Object[]> results = qry.getResultList();

    for(Object[] result : results) {
        System.out.println("-------------------------------------");
        System.out.println("Account ID is: [" + result[0] + "]");
        System.out.println("Account Name is: [" + result[1] + "]");
    }
}

JUnitTest 在控制台和 JUnit 中执行良好,没有错误或问题 windows。但是没有打印行结果。这是控制台跟踪:

[EL Fine]: sql: 2015-12-01 17:36:46.038--ServerSession(1018298342)--Connection(42338572)--Thread(Thread[main,5,main])--SELECT t0.acc_id, t2.FORENAME || ? || t2.SURNAME, t4.NAME FROM tbl_contact t4, tbl_account t3, tbl_user t2, tbl_account t1, tbl_account t0 WHERE (((t1.acc_id = t0.acc_id) OR (t3.acc_id = t0.acc_id)) AND (((t1.acc_id = t0.acc_id) AND ((t2.acc_id = t1.acc_id) AND (t1.type = ?))) AND ((t3.acc_id = t0.acc_id) AND ((t4.acc_id = t3.acc_id) AND (t3.type = ?)))))
bind => [ , u, c]

结语:

因此表明我的实体模型和继承策略是正确的,我的问题出在我的程序逻辑中,我 select 来自 "User" 和 "Contact"。

这让我得出结论,这个问题只会在我尝试查询我的两个子类时发生。请指教

检查您的继承是否正确定义并查看:

https://wiki.eclipse.org/Introduction_to_EclipseLink_JPA_%28ELUG%29#Mapping_Inheritance

我发现了由 2 个逻辑问题引起的问题的根源。

详细说明:

  • 第一个逻辑问题:

通过实施 SQL UNION 代码,我可以有效地删除 NULL 结果并将结果集与两个 SQL 语句组合。

但是,通过尝试用 SQL JOIN 代替设计,我省略了 JoinType 策略,因此它默认为 INNER JOIN。因此,代码试图检索所有记录,并且只检索完全匹配的记录。由于两个表的列结果不同,因此失败。为了克服这个问题,应该实施 LEFT JOIN 策略,它将接受 null 结果,如下所示:

解法:

@Test
public void testJoinAccounts() {
    CriteriaBuilder cBuilder = em.getCriteriaBuilder();
    CriteriaQuery<Object[]> cQuery = cBuilder.createQuery(Object[].class);
    Root<Account> accountRoot = cQuery.from(Account.class);
    Join<Account, User> joinUser = accountRoot.join(Account_.tblUser, JoinType.LEFT);
    Join<Account, Contact> joinContact = accountRoot.join(Account_.tblContact, JoinType.LEFT);
  • 第二个逻辑问题:

以上代码将执行,解决方案现在将产生结果。然而,返回的结果在左侧给出了以下效果,但在右侧是我想要实现的:

+-------+------+------+     +-------+------+
|    cID|  name|  name|     |    cID|  name|
+-------+------+------+     +-------+------+
|1      |JJAZ  |null  |     |1      |JJAZ  |
+-------+------+------+     +-------+------+
|2      |CCLL  |null  |     |2      |CCLL  |
+-------+------+------+     +-------+------+
|3      |OOBB  |null  |     |3      |OOBB  |
+-------+------+------+     +-------+------+
|4      |null  |ABCD  |     |4      |ABCD  |
+-------+------+------+     +-------+------+
|5      |null  |BCDE  |     |5      |BCDE  |
+-------+------+------+     +-------+------+
|6      |null  |CDEF  |     |6      |CDEF  |
+-------+------+------+     +-------+------+
|7      |JKNN  |null  |     |7      |JKNN  |
+-------+------+------+     +-------+------+
|8      |null  |DEFG  |     |8      |DEFG  |
+-------+------+------+     +-------+------+
|9      |RRLW  |null  |     |9      |RRLW  |
+-------+------+------+     +-------+------+
|10     |GNQN  |null  |     |10     |GNQN  |
+-------+------+------+     +-------+------+
|...    |?     |?     |     |...    |?     |
+-------+------+------+     +-------+------+

解法:

所以,我改变了原来的多选方法:

// this
cQuery.multiselect(accountRoot.get(Account_.accId), concatUserName, joinContact.get(Contact_.name))

// to this
cQuery.multiselect(accountRoot.get(Account_.accId), cBuilder.coalesce(concatUserName, joinContact.get(Contact_.name)).alias("name"))

我使用 coalesce() function 计算 (x, y) 直到它发现第一个参数值不是 null。因此,通过将用户和联系人替换为函数,这解决了我的问题。

参考:

要更详细地查看 coalesce() 函数,请访问我发布的另一个问题: