【问题标题】:Polymorphic JPA query with CriteriaQuery API使用 CriteriaQuery API 的多态 JPA 查询
【发布时间】:2013-06-20 11:50:51
【问题描述】:

我有以下实体结构:

   +-----------+                +-------------+        
   |  User     | -------------> |    Role     |
   +-----------+                +-------------+
                                       ^
                                       |
                               +-------+---------+
                               |                 |
                        +-----------+      +-----------+           +------------+
                        |   Role1   |      |  Role2    |-------->  | SomeEntity |
                        +-----------+      +-----------+           +------------+

并且我想获取其 Role2 具有 SomeEntity 具有特定属性值的所有用户的名称。 我需要使用 JPA 标准 API 来执行此操作。

到目前为止我所做的是:

CriteriaBuilder cb = ...
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<User> user = query.from(User.class);
SetJoin<User, Role> userRolesJoin = user.join(User_.roles);

// As you can see the userRolesJoin is of type Role and a Role doesn't have
// an property someEntity. So how to "cast" the userRolesJoin into an 
// SetJoin<User, Role2>.

这里如何进行多态查询?有什么建议吗?

不幸的是,JPA 标准 API 不如 Hibernate 的标准 API 直观。

【问题讨论】:

  • SQL 不是一个选项吗?这只是所有这些表之间的一个非常简单的内部连接......
  • 可能是的,但我猜JPA也应该可以,我现在不想在代码中引入sql依赖。

标签: java api jpa


【解决方案1】:

使用子查询解决它

我创建了一个基于 Role2 类型的子查询,将它与“SomeEntity”实体连接起来,并 应用了谓词。然后我使用与子查询谓词匹配的 Role2.ids 将子查询连接到“主”查询。

CriteriaBuilder cb = ...
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<User> user = query.from(User.class);
SetJoin<User, Role> userRolesJoin = user.join(User_.roles);
Path<String> roleIdPath = userRolesJoin.get(User_.id);

Subquery<String> subquery = query.subquery(String.class);
Root<Role2> role2Root = subquery.from(Role2.class);
Join<Role2, SomeEntity> someEntityJoin = role2Root.join(Role2_.someEntity);
Path<String> someEntityPropertyPath = someEntityJoin.get(SomeEntity_.aProperty);
Predicate someEntityPropertyPredicate = cb.equal(someEntityPropertyPath,
            "a property value");
subquery.select(role2Root.get(Role2_.id));
subquery.where(someEntityPropertyPredicate);

In<String> idInSubqueryIds = cb.in(roleIdPath).value(subquery);

query.select(user.get(User_.username));
query.where(idInSubqueryIds);

EntityManager entityManager = ...;
TypedQuery<String> query = entityManager.createQuery(query);
List<String> usernames = query.getResultList();

【讨论】:

    【解决方案2】:

    您可以在 Criteria 中使用 as() 进行投射。

    ((Path)user.join(User_.roles).as(Role2.class)).join(Role2_.someEntity)
    

    这可能是 JPA 2.1 之前的特定提供商,但我认为它是 JPA 2.1 中的标准。

    在 JPQL 中,您可以使用 TREAT 操作。

    http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL#Special_Operators

    也可以使用第二个from(),

    Root<Role2> role2 = query.from(Role2.class);
    ... cb.equal(user.join(User_.roles), role2)
    

    【讨论】:

    • 是的,我也找到了这个解决方案,但它只适用于 eclipselink。它不适用于 openjpa,因为在 OpenJPA 中,“as”方法返回一个 Expressions$CastAs 对象,而这并没有实现 Path。我正在使用 openjpa,因此它不是我的解决方案。
    • 改用Treat。 'AS' 作为未正式化的早期版本的向下转换。规范改为使用 Treat。
    猜你喜欢
    • 1970-01-01
    • 2015-12-17
    • 2014-10-05
    • 2012-06-15
    • 2012-07-05
    • 2011-01-31
    • 2011-03-29
    • 1970-01-01
    • 2021-04-03
    相关资源
    最近更新 更多