【问题标题】:Criteria API: Select Expression IF [NOT] NULL <value>条件 API:选择表达式 IF [NOT] NULL <值>
【发布时间】:2018-05-29 13:09:57
【问题描述】:

我正在尝试将以下简单查询转换为 JPA 标准 API:

SELECT ticket_action.ticket_id IS NOT NULL, ticket.id
FROM ticket
LEFT OUTER JOIN ticket_action ON ticket.id = ticket_action.ticket_id

我尝试了两种方法:

  1. 使用criteriaBuilder.function("ISNULL", Boolean.class, ticketAction.get(TicketAction_.ticketId)):问题是如果ISNULLISNOTNULL 类似(对于MySQL/MariaDB 数据库),则没有否定性
  2. 使用criteriaBuilder.isNotNull(ticketAction.get(TicketAction_.ticketId)):但这会返回Predicate,而select语句只接受Expressions

拉伸目标:同GROUP BY ticket.ticket_id IS NOT NULL


请求的表结构

+-----------+---------+
|         ticket      |
+-----------+---------+
|   name    |  type   |
+-----------+---------+
| id        | integer |
| title     | varchar |
+-----------+---------+

+-----------+---------+
|    ticket_action    |
+-----------+---------+
|   name    |  type   |
+-----------+---------+
| id        | integer |
| ticket_id | integer |
| title     | varchar |
+-----------+---------+

结果:

最后,我最终使用了一个构造函数查询,它接收到 ticket_actionid 等。在构造函数中有关于状态的决定:this.hasTicketAction = ticketActionId != null; 不是很满意,但工作正常。

感谢您发布您的答案!

【问题讨论】:

  • 是否可以查看您的实体? @OneToOne?还有 3-4 行你期望的结果是什么?是不是像true, 1: false, 2; false,3, true:4
  • 对于延伸目标的GROUP BY:有一个聚合或分组什么?
  • 更新了我的答案。你能检查一下这个GROUP BY ticket.ticket_id IS NOT NULL吗?
  • 您的第二种方法描述是错误的。 isNotNull 返回一个Predicate&lt;Boolean&gt;,它确实是一个Expression&lt;Boolean&gt;,并且还扩展了Selection&lt;Boolean&gt;。问题是 Boolean 泛型类型与 CriteriaQuery 泛型类型定义的任何内容发生冲突。也许如果您发布了查询结构的其余部分,那么它将是一个更完整的定义......

标签: java jpa criteria-api


【解决方案1】:

对于这个查询,您需要有一个@OneToMany 类型的集合属性。 另外我不得不使用Tuple 而不是TicketTuple(在this 答案中引用),因为它在处理没有票的ticket_actions 时给出了IllegalArgumentException(参数类型不匹配),我不知道你是否有您的架构中存在这种可能性。

EclipseLink JPA 实现,Derby db。

Ticket 类中,你必须拥有这个属性:

@OneToMany(mappedBy = "ticket")
private Collection<TicketAction> ticketActions;

条件查询代码:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class);
Root<Ticket> from = cq.from(Ticket.class);
Join<Ticket, TicketAction> join = from.join("ticketActions", JoinType.LEFT);

Expression<Boolean> caseHasAction = cb.<Boolean>selectCase()
      .when(cb.isNotNull(join.get("id")), true)
      .otherwise(false);

cq.multiselect(
      caseHasAction.as(Boolean.class)
     ,from.get("id")
);
TypedQuery<Tuple> tq = em.createQuery(cq);

for (Tuple tt : tq.getResultList()) {
    System.out.println("Res: " + tt.get(0) + " " + (tt.get(1) == null ? "null" : tt.get(1).toString()));
}

结果查询:

SELECT CASE WHEN (t0.ID IS NOT NULL) THEN 1 ELSE 0 END , t1.ID FROM TICKET t1 LEFT OUTER JOIN TICKET_ACTION t0 ON (t0.TICKET_ID = t1.ID)

【讨论】:

    【解决方案2】:

    一种工作方法是使用CriteriaBuilder.selectCase(..)

    DTO 类似(Object[] 也是如此,但从一开始就是类型安全的)

    @Getter
    @AllArgsConstructor
    public class TicketTuple {
       private Boolean hasAction;
       private Long ticketId;
    }
    

    可以通过以下查询获得结果

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<TicketTuple> cq = cb.createQuery(TicketTuple.class);
    Root<Ticket> from = cq.from(Ticket.class);
    Join<Ticket, TicketAction> join = from.join("ticketAction", JoinType.LEFT);
    
    Expression<Boolean> caseHasAction = cb.<Boolean>selectCase()
          .when(cb.isNotNull(join), true)
          .otherwise(false);
    
    cq.multiselect(
          caseHasAction.as(Boolean.class)
         ,from.get("id")
    );
    

    关于分组,我不太明白你需要分组,但是例如

    cq.multiselect(
         caseHasAction.as(Boolean.class)
         ,cb.count(from.get("id"))
    ).groupBy(caseHasAction.as(Boolean.class));
    

    将返回具有和不具有TicketActionTickets 的计数。

    我不知道实际原因,但问题似乎是cb.isNotNull(..) (my) JPA 实现 either 总是获取似乎不是的东西是null ,具体取决于检查null 的方式,它会创建一些INNER JOIN,以便从结果集中删除null 值。

    (我正在使用 OpenJPA)

    【讨论】:

    • 虽然这看起来很有希望,但它创建了一个奇怪的 SQL 查询,例如 SELECT …, CASE WHEN NOT (NOT EXISTS (SELECT t6.ID FROM ticket_action t7, ticket t6 WHERE (t7.ticket_id = t1.ID)) ) THEN ? ELSE ? END FROM …。分组什么?:根据关系是否存在:group by ticket_action.id IS NOT NULL
    【解决方案3】:

    您可以使用下面的示例在选择表达式中生成 is not null

    CriteriaQuery<Tuple> cq = entityManager.getCriteriaBuilder().createTupleQuery();
    Root<Test> pet = cq.from(Test.class);
    cq.multiselect(pet.get("id1").isNotNull(), pet.get("id2"));
    

    我认为生成的查询将引发查询语法异常,因为 jpa/hibernate 可能不支持在选择表达式中具有 is not null。如果是这种情况,那么一种可能的解决方案是将 is not null 放在 where 子句中。您可以在此处找到该案例的示例https://docs.oracle.com/cd/E19798-01/821-1841/gjitv/index.html

    【讨论】:

    • 这就是我在第二种方法中所做的,但失败了。
    猜你喜欢
    • 1970-01-01
    • 2011-05-13
    • 2011-08-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-19
    • 2023-03-27
    相关资源
    最近更新 更多