使用jpa标准选择其他表中每种状态的出现次数

时间:2019-03-24 09:56:56

标签: java jpa spring-data-jpa criteria-api

我只需要在一个查询中使用条件api选择主表以及每个状态在另一个表中的出现次数。

我当前的解决方案是在本机查询中运行,但我想以一种基于对象的方式进行查询。 我尝试通过使用特定查询来按条件进行操作,只是选择所有状态,然后手动进行计数。但是使用这种方法,我要调用两个查询:一个在我的主表中获取详细信息,另一个在ID与主表相同的情况下选择所有状态。 有更有效的方法吗?

这是我的本机查询(简体):

SELECT * FROM(
SELECT a.id, a.type, b.count_pending, b.count_failed, b.count_processed
FROM CM AS a
LEFT JOIN ( SELECT 
 COUNT( CASE WHEN status = 'PENDING' THEN 1 ELSE NULL END ) count_pending,
 COUNT( CASE WHEN status = 'FAILED' THEN 1 ELSE NULL END ) count_failed,
 COUNT( CASE WHEN status = 'PROCESSED' THEN 1 ELSE NULL END ) count_processed
 FROM CM_PARAM WHERE id_cm = :cmId
 GROUP BY id_cm    
) AS b ON a.id_cm = b.id_cm
WHERE a.id_cm = :cmId) AS a

这是我的CM实体(简体):

@Entity
public class Cm {

  @Id
  private Long idCm;

  private String type;

  // other fields
  // setters and getters

}

这是我的CM_PARAM实体(简体):

@Entity
public class CmParam {

  @Id
  private Long idCmp;

  @ManyToOne
  @JoinColumn(name = "id_cm")
  private Cm cm;

  private String status;

  // other fields
  // setters and getters
}

使用本机查询方法,我可以在Cm实体中添加临时字段:

@Transient
private Long countPending;

@Transient
private Long countFailed;

@Transient
private Long countProcessed;

如何使用标准api进行操作,如果可能的话,仅进行一次交易。

预期输出将是这样的:

{
  "idCm": 1,
  "type": "sms",
  "countPending": 5,
  "countFailed": 3,
  "countProcessed": 9
}

1 个答案:

答案 0 :(得分:0)

无需子查询联接就可以重写您的查询:

SELECT
    a.id_cm,
    a.type
    COUNT(CASE WHEN b.status = 'PENDING' THEN 1 ELSE NULL END) countPending,
    COUNT(CASE WHEN b.status = 'FAILED' THEN 1 ELSE NULL END) countFailed,
    COUNT( CASE WHEN b.status = 'PROCESSED' THEN 1 ELSE NULL END ) countProcessed
FROM CM AS a
LEFT JOIN CM_PARAM AS b ON a.id_cm = b.id_cm
WHERE a.id_cm = ?1
GROUP BY a.id_cm, a.type

您必须将关联的反面添加到Cm

@OneToMany(mappedBy = "cm")
private Set<CmParam> params;

(否则,您需要从RIGHT JOINCmParam的{​​{1}},这是Hibernate不支持的)

条件查询变为:

Cm

请注意,结果的类型为CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<? extends Object[]> cq = cb.createQuery(new Object[0].getClass()); Root<Cm> a = cq.from(Cm.class); Join<Cm, CmParam> b = a.join("params", JoinType.LEFT); cq.where(cb.equal(a.get("idCm"), cb.parameter(Long.class, "idCm"))); cq.groupBy(a.get("idCm"), a.get("type")); cq.multiselect( a.get("idCm"), a.get("type"), cb.count(cb.selectCase() .when(cb.equal(b.get("status"), "PENDING"), 1L) .otherwise(cb.nullLiteral(Long.class))), cb.count(cb.selectCase() .when(cb.equal(b.get("status"), "FAILED"), 1L) .otherwise(cb.nullLiteral(Long.class))), cb.count(cb.selectCase() .when(cb.equal(b.get("status"), "PROCESSED"), 1L) .otherwise(cb.nullLiteral(Long.class)))); 。如果要对瞬态字段使用当前方法,最简单的方法是将适当的构造函数添加到Object[]并使用Cm方法:

cb.construct()

请注意:

  • 如果您不想将cq.select(cb.construct(Cm.class, a.get("idCm"), a.get("type"), ...)) 字段添加到params,但对Cm没问题,则可以只使用INNER JOIN和{{1 }}。
  • 如果在实际查询中,您从Root<CmParam> b = cq.from(CmParam.class)中选择的属性不只是Join<CmParam, Cm> a = b.join("cm")Cm,那么您可能还需要在cmId中列出所有属性