Spring JPA:订单与状态表关联设计及动态查询方案咨询
订单与状态审计表的实体设计及动态查询实现
一、实体类正确设计方式
根据需求场景(单独查询订单/查询订单+最新状态),实体设计需兼顾性能与灵活性,核心是使用懒加载避免不必要的关联查询,同时定义DTO封装前端需要的返回结构。
1. 订单实体(对应FX_ORDER表)
@Entity @Table(name = "FX_ORDER") public class Order { @Id @Column(name = "ORDER_ID") private String orderId; @Column(name = "SEQUENCE") private Integer sequence; @Column(name = "PRODUCT") private String product; // 懒加载关联状态历史,仅主动调用时才查询STATUS表 @OneToMany(mappedBy = "order", fetch = FetchType.LAZY) private List<OrderStatus> statusHistory; // 构造器、Getter、Setter省略 }
2. 状态审计实体(对应STATUS表)
@Entity @Table(name = "STATUS") public class OrderStatus { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "ORDER_ID") private String orderId; @Column(name = "SEQUENCE") private Integer sequence; @Column(name = "STATUS_CODE") private String statusCode; @Column(name = "LAST_UPDATED_BY") private String lastUpdatedBy; @Column(name = "LAST_UPDATED_DATE_TIME") private LocalDateTime lastUpdatedDateTime; // 懒加载关联订单,避免循环加载 @ManyToOne(fetch = FetchType.LAZY) @JoinColumns({ @JoinColumn(name = "ORDER_ID", referencedColumnName = "ORDER_ID", insertable = false, updatable = false), @JoinColumn(name = "SEQUENCE", referencedColumnName = "SEQUENCE", insertable = false, updatable = false) }) private Order order; // 构造器、Getter、Setter省略 }
3. 返回结果DTO
用于精准返回前端需要的字段结构:
public class OrderWithLatestStatusDTO { private String orderId; private String product; private String status; private String lastUpdatedBy; // 用于Criteria Builder投影查询的构造器 public OrderWithLatestStatusDTO(String orderId, String product, String status, String lastUpdatedBy) { this.orderId = orderId; this.product = product; this.status = status; this.lastUpdatedBy = lastUpdatedBy; } // Getter省略 }
关联查询性能说明
使用FetchType.LAZY后,调用Spring Data JPA的findByXXX方法仅查询订单表,不会自动拉取STATUS表数据,只有主动调用order.getStatusHistory()时才会触发关联查询,完全满足“有时仅查询订单详情”的需求。
二、用Criteria Builder实现动态查询获取最新状态记录
以下代码实现了与示例SQL等价的逻辑,同时支持动态添加WHERE子句(比如动态过滤statusCode):
@Repository public class OrderCustomRepositoryImpl implements OrderCustomRepository { @PersistenceContext private EntityManager entityManager; @Override public List<OrderWithLatestStatusDTO> findOrdersWithLatestStatus(String statusCode) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<OrderWithLatestStatusDTO> query = cb.createQuery(OrderWithLatestStatusDTO.class); // 主查询根:订单表 Root<Order> orderRoot = query.from(Order.class); // 子查询:获取每个(ORDER_ID, SEQUENCE)对应的最新状态更新时间 Subquery<LocalDateTime> maxDateTimeSubquery = query.subquery(LocalDateTime.class); Root<OrderStatus> statusSubRoot = maxDateTimeSubquery.from(OrderStatus.class); maxDateTimeSubquery.select(cb.max(statusSubRoot.get("lastUpdatedDateTime"))) .where( cb.equal(statusSubRoot.get("orderId"), orderRoot.get("orderId")), cb.equal(statusSubRoot.get("sequence"), orderRoot.get("sequence")) ); // 左连接到最新的状态记录,关联条件匹配订单标识+最新时间 Join<Order, OrderStatus> statusJoin = orderRoot.join("statusHistory", JoinType.LEFT); statusJoin.on( cb.equal(statusJoin.get("orderId"), orderRoot.get("orderId")), cb.equal(statusJoin.get("sequence"), orderRoot.get("sequence")), cb.equal(statusJoin.get("lastUpdatedDateTime"), maxDateTimeSubquery) ); // 动态构建WHERE谓词 List<Predicate> predicates = new ArrayList<>(); // 示例:动态添加statusCode过滤条件 if (statusCode != null && !statusCode.isBlank()) { predicates.add(cb.equal(statusJoin.get("statusCode"), statusCode)); } // 可添加更多动态条件,比如订单ID范围、产品类型等 query.where(predicates.toArray(new Predicate[0])); // 投影到DTO,直接返回需要的字段 query.select(cb.construct( OrderWithLatestStatusDTO.class, orderRoot.get("orderId"), orderRoot.get("product"), statusJoin.get("statusCode"), statusJoin.get("lastUpdatedBy") )); // 去重避免重复结果 query.distinct(true); return entityManager.createQuery(query).getResultList(); } }
代码逻辑说明
- 子查询:确定每个订单(以
ORDER_ID+SEQUENCE为唯一标识)的最新状态更新时间,确保只关联最新的一条状态记录。 - 动态谓词:通过
List<Predicate>可以灵活添加多个查询条件,满足动态WHERE的需求。 - 投影查询:直接构造DTO返回,避免加载整个实体,提升查询性能并匹配前端要求的返回结构。
内容的提问来源于stack exchange,提问作者Deepa
相关产品推荐
相关产品推荐

