04-Repository派生查询-八个接口零SQL黒漂技术佬 · AI 伙伴AI-Partner「数据接口部署与二次开发」系列 04上一系列讲完实体这篇看数据访问层。AI 伙伴的 repository 包里有 8 个接口全部继承JpaRepository加起来 22 个查询方法——没有一行 SQL没有一个 Query 注解。查询全靠方法名自己会说话的派生查询Derived Query。这篇把这套命名语法彻底拆开让你以后看到方法名就知道它翻译成什么 SQL。一、先看一个最短的例子// 项目源码repository/UserRepository.javapublicinterfaceUserRepositoryextendsJpaRepositoryUser,Long{OptionalUserfindByOpenId(StringopenId);OptionalUserfindByPhone(Stringphone);}就这么两行。Spring Data JPA 在启动时解析方法名findByOpenIdfindBy是关键字前缀OpenId对应实体属性openId自动生成select * from t_user where open_id ?。返回OptionalUser表示可能查不到。其他 7 个 Repository 同理全是这个玩法。二、派生查询命名语法完整拆解方法名 关键词 属性表达式 排序子句关键词之间可以自由组合。下面这张表把本项目用到的全部关键词列齐关键词语义项目实例findBy查询等值条件findByOpenIdcountBy计数countByUserIdAndActiveTrueAnd条件连接ANDfindByUserIdAndActiveTrueAndContentContainingOrderBy...Desc/Asc排序OrderByImportanceDescCreatedAtDescBetween区间含边界findByUserIdAndRecordedAtBetween...After/LessThanEqual大于 / 小于等于AndRecordedAtAfter、RemindTimeLessThanEqualContainingLIKE ‘%xxx%’AndContentContainingIsTrueActiveTrue布尔 trueActiveTrueTopN取前 N 条findTop10ByUserId...、findTop20ByUserId...Pageable参数分页findByUserIdOrderByCreatedAtDesc(Long, Pageable)几个容易犯迷糊的点Containing是两边带 % 的模糊匹配生成content LIKE %关键词%。项目用它做记忆的关键词检索。Between是闭区间两端都含传now().minusDays(7)和now()就是近 7 天含今天。TopN放在 findBy 后面findTop10ByUserIdOrderByRecordedAtDescORDER BY recorded_at DESC LIMIT 10。Pageable是参数不是方法名的一部分方法名照常写加一个Pageable形参即可分页返回类型可以是ListT或PageT。对话历史查询就是findByUserIdOrderByCreatedAtDesc(Long userId, Pageable pageable)。排序可以多级OrderByImportanceDescCreatedAtDesc 先按重要度降序重要度相同再按创建时间降序。三、八个 Repository 方法清单逐个过Repository方法业务用途与 SQL 语义UserRepositoryfindByOpenId(String)登录即查找按平台 openId 找用户找不到就注册findByPhone(String)按手机号找回用户MemoryItemRepositoryfindByUserIdAndActiveTrueOrderByImportanceDescCreatedAtDesc本项目的灵魂查询取该用户未删除的记忆重要度优先、新记忆优先用于注入 Agent 系统提示词前 20 条findByUserIdAndActiveTrueAndTypeOrderByCreatedAtDesc(Long, String)按记忆类型fact/preference/relation/event/health过滤查看findByUserIdAndActiveTrueAndContentContaining(Long, String)关键词搜记忆countByUserIdAndActiveTrue(Long)记忆条数统计ReminderRepositoryfindByUserIdAndStatusOrderByRemindTimeAsc(Long, String)用户待触发提醒列表按触发时间升序最近的排最前findByStatusAndRemindTimeLessThanEqualAndPushedFalse(String, LocalDateTime)定时任务专用扫pending 且 remindTime ≤ 当前且未推送的到期提醒每分钟执行一次findByUserId(Long)用户全部提醒ConversationRepositoryfindByUserIdOrderByCreatedAtDesc(Long, Pageable)分页拉对话历史新的在前countByUserId(Long)对话总轮数EmotionRecordRepositoryfindByUserIdAndRecordedAtBetweenOrderByRecordedAtDesc(Long, LocalDateTime, LocalDateTime)时间段情绪记录做情绪趋势findTop10ByUserIdOrderByRecordedAtDesc(Long)最近 10 条情绪Agent 查用户最近心情用HealthRecordRepositoryfindByUserIdAndTypeAndRecordedAtBetweenOrderByRecordedAtDesc(Long, String, LocalDateTime, LocalDateTime)近 7 天某类健康数据趋势心率/血压曲线的底层findTop20ByUserIdOrderByRecordedAtDesc(Long)最近 20 条健康记录findByUserIdAndAbnormalTrueAndRecordedAtAfter(Long, LocalDateTime)某时间之后的异常记录健康巡检用DeviceRepositoryfindByDeviceCode(String)按设备编码查设备注册、绑定、下发指令的入口查询findByUserId(Long)用户绑定设备列表findByStatus(String)/countByStatus(String)按在线状态筛设备 / 统计在线数AlertRepositoryfindByStatusOrderByCreatedAtDesc(String)全部待处理工单新的在前findByUserIdAndStatusOrderByCreatedAtDesc(Long, String)某用户的工单列表可以观察到一条规律每个方法的形态都精确对应一个业务场景。Agent 拼 prompt要重要度排序就写OrderByImportanceDesc定时扫到期要时间比较加未推送过滤就写LessThanEqual加PushedFalse。方法名不是随手起的是需求直接翻译。四、为什么这个项目不需要 QueryQuery用来手写 JPQL 或原生 SQL适合派生查询表达不了的场景。AI 伙伴全部查询能靠方法名搞定根本原因是所有查询都是单表、等值/区间/排序/取前的组合没有跨表 JOIN没有复杂聚合。这又回到了第一篇说的无外键、实体零关联的设计——既然实体互不引用就永远不需要 JOIN派生查询的舒适区就永远够用。这是个漂亮的自洽表设计决定了查询形态查询形态决定了数据访问层的写法。你二次开发时如果延续单表职责、逻辑关联的风格就能继续享受零 SQL一旦打破就得换工具。五、派生查询的边界什么时候该换写法四种情况派生查询会开始吃力多表 JOIN比如查所有今天触发过跌倒告警的用户及其设备名跨t_alert和t_device方法名写不出来老老实实Query写 JPQL join或者拆成两次单表查询在 Service 里组装。复杂聚合GROUP BY type HAVING COUNT(*) 5这类统计方法名无能为力。动态条件查询条件由前端传参决定可选 userId、可选 status、可选时间段方法名组合爆炸。此时该上Specification或QueryDSL。性能敏感的批量操作Modifying Query批量 UPDATE/DELETE 比逐条 save 快得多。拿本项目举个反例也能说明问题EmotionService.recent想查最近 N 条情绪但 Repository 只定义了findTop10ByUserIdOrderByRecordedAtDesc固定取 10 条——Controller 传的 limit 参数实际是取回来后在内存里截断的。这就是派生查询参数化 LIMIT不方便的缩影不如一个Query(select e from EmotionRecord e where e.userId:uid order by e.recordedAt desc)配Pageable来得干脆。六、命名过长的可读性权衡findByUserIdAndActiveTrueOrderByImportanceDescCreatedAtDesc——56 个字符的方法名初见吓一跳。但换个角度它把查询意图完整写在了签名里IDE 补全一敲就出来review 代码时不用跳到 SQL 里猜语义。我个人觉得这比getUserMemoriesV2()这种黑盒命名诚实得多。权衡建议方法名控制在能读出查什么、按什么过滤、怎么排序的程度超过三个 And 条件就考虑加注释说明业务含义五个以上就该怀疑是不是该换 Query 了。七、可运行的验证方式打开 show-sql想知道每个方法名翻译成了什么 SQL最直接的办法是把show-sql打开跑一遍# application.yml 开发环境临时调整spring:jpa:show-sql:trueproperties:hibernate:format_sql:true然后启动后端调一次接口如POST /api/chat让 Agent 查记忆控制台就能看到生成语句-- 示意Hibernate 实际输出字段有删节select...fromt_memorywhereuser_id?andactive1orderbyimportancedesc,created_atdesc;对照方法名逐段核对UserId→user_id?、ActiveTrue→active1、OrderByImportanceDescCreatedAtDesc→order by importance desc, created_at desc。验完记得关掉 show-sql别把调试开关带上线。八、合规提醒记忆、情绪、健康三个 Repository 的查询结果都会拼进大模型的提示词等于把用户的敏感数据交给外部大模型服务处理。二次开发扩展查询时注意查询粒度最小化不要一次捞全量历史、在日志里脱敏show-sql 打开时对话和健康数据会打进日志生产环境严禁、返回给前端前做权限校验只能查当前授权用户自己的数据。小结派生查询是把查询需求直接写进方法名的声明式风格——单表、简单条件时它是最优雅的选择AI 伙伴 8 个 Repository、22 个方法、零 SQL 就是最有力的示范。但它的舒适区有明确边界JOIN、聚合、动态条件一旦出现就该请 Query 和 Specification 出山。下一篇我们站到接口层看 9 个 Controller 是怎么把这套数据能力组织成 20 个 HTTP 接口的。
阅读完成 · 觉得有帮助?