如何基于Specifications与Criteria Builder实现关联子查询编写
基于Criteria Builder实现取消息关联最新明细的查询方案
基础信息
- 实体结构定义
class MessageEntity{ long id; List<MessageDetailEntity> message_details; } class MessageDetailEntity{ long id; Date created_ts; }
- 目标对齐SQL逻辑
select * from message msg inner join message_detail msd on msg.id = msd.message_id where msd.id = (select id from message_detail msd1 where msd1.message_id = msg.id order by msd1.created_ts desc limit 1);
- 业务规则:单条
MessageEntity可关联多条MessageDetailEntity记录,查询核心是筛选出每个消息下创建时间最新的明细关联的消息全量数据。 - 已编写的零散代码片段
Subquery<MessageDetailEntity> msd = query.subquery(MessageDetailEntity.class); Root<MessageEntity> sm = smd.from(SecureMessageDetailEntity.class); Join<MessageDetailEntity, MessageEntity> join = query.join("messageDetails");
现有代码的问题点
- 根实体定义和查询目标不匹配:错误使用了不存在的
SecureMessageDetailEntity作为根节点,而最终要查询的主体是MessageEntity - 关联方向反转:Join需要从主查询的根节点
MessageEntity出发,关联对应的message_details集合,而非反过来从明细关联消息 - 子查询未绑定主查询关联关系:没有建立子查询内的明细实体和主查询消息实体的外键关联,无法实现逐行匹配当前消息下最新明细的逻辑
可落地的实现代码
原生Criteria Builder实现
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<MessageEntity> query = cb.createQuery(MessageEntity.class); // 主查询根节点,对应message表 Root<MessageEntity> messageRoot = query.from(MessageEntity.class); // 构建子查询:匹配当前消息下创建时间最新的明细id Subquery<Long> latestDetailIdSub = query.subquery(Long.class); Root<MessageDetailEntity> subDetailRoot = latestDetailIdSub.from(MessageDetailEntity.class); latestDetailIdSub.select(subDetailRoot.get("id")) .where( // 子查询关联条件:明细所属消息id 等于 主查询当前的消息id cb.equal(subDetailRoot.get("message").get("id"), messageRoot.get("id")) ) .orderBy(cb.desc(subDetailRoot.get("created_ts"))); // 子查询限制只取排序后第一条 latestDetailIdSub.setMaxResults(1); // 主查询关联消息对应的明细集合,做匹配过滤 Join<MessageEntity, MessageDetailEntity> detailJoin = messageRoot.join("message_details"); query.select(messageRoot) .where(cb.equal(detailJoin.get("id"), latestDetailIdSub)); // 执行查询 List<MessageEntity> resultList = entityManager.createQuery(query).getResultList();
Spring Data JPA Specifications 适配实现
如果项目基于Spring Data JPA的Specifications体系开发,可以封装成可复用的查询条件:
public static Specification<MessageEntity> matchLatestMessageDetail() { return (root, query, cb) -> { // 构建逐行匹配的最新明细子查询 Subquery<Long> latestDetailIdSub = query.subquery(Long.class); Root<MessageDetailEntity> subDetailRoot = latestDetailIdSub.from(MessageDetailEntity.class); latestDetailIdSub.select(subDetailRoot.get("id")) .where(cb.equal(subDetailRoot.get("message").get("id"), root.get("id"))) .orderBy(cb.desc(subDetailRoot.get("created_ts"))); latestDetailIdSub.setMaxResults(1); // 关联明细实体做等值匹配 Join<MessageEntity, MessageDetailEntity> detailJoin = root.join("message_details"); return cb.equal(detailJoin.get("id"), latestDetailIdSub); }; }
注意事项:需要确保
MessageDetailEntity中已定义和MessageEntity的外键关联映射,否则会出现字段找不到的运行错误;上述代码中subDetailRoot.get("message")的字段名需要和实体中实际定义的关联字段名保持一致。
内容的提问来源于stack exchange,提问作者CodeHunter
相关产品推荐
相关产品推荐

