Spring Boot Criteria Query实现一对多关联的最新状态查询
用JPA Criteria Query实现最新状态为Pending的交易查询
需求分析
我们要筛选出满足两个核心条件的交易:
- 该交易的最新状态是Pending;
- 该交易在这个Pending状态之后没有任何其他状态记录(比如Completed、Failed)。
实体类关联定义
先确保实体类正确配置一对多关联:
@Entity @Table(name = "transaction") public class Transaction { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long credId; private Long debtId; private BigDecimal amount; private BigDecimal charges; // 一对多关联交易状态表 @OneToMany(mappedBy = "transaction", fetch = FetchType.LAZY) private List<TransactionStatus> statuses; // Getters & Setters } @Entity @Table(name = "transaction_status") public class TransactionStatus { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String status; private LocalDateTime created; // 多对一关联交易表 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "transaction_id") private Transaction transaction; // Getters & Setters }
Criteria Query 实现代码
下面是完整的查询实现,包含子查询处理核心逻辑:
import jakarta.persistence.criteria.*; import java.time.LocalDateTime; import java.util.List; // 假设这是你的Repository或Service类中的方法 public List<Transaction> findLatestStatusPendingTransactions() { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Transaction> cq = cb.createQuery(Transaction.class); Root<Transaction> transactionRoot = cq.from(Transaction.class); // 子查询1:获取每个交易的最新状态记录的创建时间 Subquery<LocalDateTime> latestStatusTimeSubquery = cq.subquery(LocalDateTime.class); Root<TransactionStatus> latestStatusRoot = latestStatusTimeSubquery.from(TransactionStatus.class); latestStatusTimeSubquery.select(cb.greatest(latestStatusRoot.get("created"))) .where(cb.equal(latestStatusRoot.get("transaction").get("id"), transactionRoot.get("id"))); // 子查询2:筛选出那些在最新Pending状态之后还有其他非Pending状态的交易ID(需要排除这些交易) Subquery<Long> excludedTxIdsSubquery = cq.subquery(Long.class); Root<TransactionStatus> excludedStatusRoot = excludedTxIdsSubquery.from(TransactionStatus.class); excludedTxIdsSubquery.select(excludedStatusRoot.get("transaction").get("id")) .where( // 状态创建时间晚于该交易的最新状态时间(也就是后续有新状态) cb.greaterThan(excludedStatusRoot.get("created"), latestStatusTimeSubquery), // 后续状态不是Pending cb.notEqual(excludedStatusRoot.get("status"), "Pending") ); // 关联交易和状态表,查询最新状态为Pending的记录 Join<Transaction, TransactionStatus> statusJoin = transactionRoot.join("statuses", JoinType.INNER); cq.select(transactionRoot) .distinct(true) // 避免一对多关联导致的重复结果 .where( // 当前状态是Pending cb.equal(statusJoin.get("status"), "Pending"), // 该状态是该交易的最新状态 cb.equal(statusJoin.get("created"), latestStatusTimeSubquery), // 排除那些有后续非Pending状态的交易 cb.not(transactionRoot.get("id").in(excludedTxIdsSubquery)) ); return entityManager.createQuery(cq).getResultList(); }
关键逻辑说明
- 子查询1:通过
cb.greatest()聚合函数,获取每个交易所有状态记录中最新的创建时间,确保我们只关注交易的最后一次状态变更。 - 子查询2:找出那些在最新状态之后还产生了非Pending状态的交易ID,这些交易不符合需求,需要从结果中排除。
- 主查询:通过内连接关联交易和状态表,筛选出最新状态为Pending的交易,同时排除掉有后续状态的交易,用
distinct(true)避免一对多关联带来的重复数据。
动态查询扩展
如果需要添加动态过滤条件(比如按credId、金额范围筛选),可以直接在主查询中追加条件:
// 示例:动态添加credId过滤 Long targetCredId = 123L; if (targetCredId != null) { Predicate credIdPredicate = cb.equal(transactionRoot.get("credId"), targetCredId); cq.where(cb.and(cq.getRestriction(), credIdPredicate)); } // 示例:动态添加金额范围过滤 BigDecimal minAmount = new BigDecimal("100"); BigDecimal maxAmount = new BigDecimal("1000"); if (minAmount != null && maxAmount != null) { Predicate amountRangePredicate = cb.between(transactionRoot.get("amount"), minAmount, maxAmount); cq.where(cb.and(cq.getRestriction(), amountRangePredicate)); }
内容的提问来源于stack exchange,提问作者Shyam Sawant
相关产品推荐
相关产品推荐

