问题简报:
我正在尝试查询我的数据库,其中我将模型映射为超类 - 子类一对一模型。因此,"帐户"是超类,它由以下子类之一扩展,它只能是一个子类类型,而不是两者。
超类拥有所有"账户"子类的类型为" 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;。
这让我得出结论,这个问题只发生在我试图查询我的两个子类时。请指教。
答案 0 :(得分:1)
检查您的继承是否已正确定义并查看:
https://wiki.eclipse.org/Introduction_to_EclipseLink_JPA_%28ELUG%29#Mapping_Inheritance
答案 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