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

时间:2015-11-30 11:46:42

标签: jpa join eclipselink criteria multi-select

问题简报:

我正在尝试查询我的数据库,其中我将模型映射为超类 - 子类一对一模型。因此,"帐户"是超类,它由以下子类之一扩展,它只能是一个子类类型,而不是两者。

超类拥有所有"账户"子类的类型为" User"和"联系"。我需要从超类查询,"帐户"因为我必须动态检查可能存在于任何一个子类中的一对一关系。

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

@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"的子类。和"联系"它扩展并继承自" 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)"帐户","用户"和"联系"详细信息,以便它匹配一个工作的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;

即,我选择" User"和"联系"并将用户的CONCATENATED名称与联系人(通过UNION)相匹配,其中包含来自" Account"的ID。此SQL返回所有帐户,每个帐户具有相应的名称。 事后看来,我会替换这两行:

  

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

  

//在此处插入特定帐户ID以查找特定帐户属于哪个子类   ON tu。" acc_id" = 120(或)ON tc。" acc_id" = 120(分别)

尝试:

下面是我使用JUnit Test尝试使用CriteriaAPI的方法。我使用"帐户"作为root并在使用元模型之前将子类声明为Join对象(从而确保我使用正确的属性)来拉取查询中的相应字段。

(我意识到返回一个List of 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在Console和JUnit窗口中执行正常,没有错误或问题。但没有打印线结果。这是控制台跟踪:

[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]

结束声明:

因此建议我的实体模型和继承策略是正确的,我的问题在于我的程序逻辑,我从两者中选择&#34; User&#34;和&#34;联系&#34;。

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

2 个答案:

答案 0 :(得分:1)

答案 1 :(得分:0)

我发现了由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()功能,请访问我发布的另一个问题:
SQL: How to combine (merge) similar columns to remove NULLs via JOIN