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()
函数,请访问我发布的另一个问题:
问题简介:
我正在尝试查询我已将模型映射为超类-子类一对一模型的数据库。就是说,"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()
函数,请访问我发布的另一个问题: