bigywater.tech
Spring · 88

派生查询:方法名怎么变成 SQL

方法名里的 findByAgeBetween、Top3、IgnoreCase 到底生成什么 SQL?Containing 里的 % 会不会被当通配符?deleteBy 是一条 delete 吗?方法名写错了什么时候才发现?这篇在 Spring Boot 4.0.8、Hibernate 7、MySQL 上把 20 多个方法名都跑一遍并抓出 SQL。

3 行 → 1 + 3 条 SQL
deleteByCity 删 3 行,先 1 条 select 再逐行 3 条 delete
1 vs 2 行
StartingWith("50%") 命中 1 行,Like("50%") 命中 2 行:前者转义了 %
启动就失败
方法名里属性写错,Did you mean 'lastName',不是等到调用时

方法名 → SQL

实测 · 本机执行输出
findByLastName("Lee")  ⇒ 返回 3 行 [Ann Lee, Bob Lee, Fay lee]
    select … from person p1_0 where p1_0.last_name=?
findByFirstNameAndLastName("Ann","Lee")  ⇒ 返回 1 行 [Ann Lee]
    select … from person p1_0 where p1_0.first_name=? and p1_0.last_name=?
findByFirstNameOrLastName("Ann","Ray")  ⇒ 返回 3 行 [Ann Lee, Cid Ray, Dee Ray]
    select … from person p1_0 where p1_0.first_name=? or p1_0.last_name=?
findByAgeGreaterThanEqual(40)  ⇒ 返回 4 行 [Cid Ray, 50%off Sale, 500off Sale, Fay lee]
    select … from person p1_0 where p1_0.age>=?
findByAgeBetween(20,30)  ⇒ 返回 3 行 [Ann Lee, Bob Lee, Eve Poe]
    select … from person p1_0 where p1_0.age between ? and ?
findByAgeIn([19,22,25])  ⇒ 返回 3 行 [Bob Lee, Dee Ray, Eve Poe]
    select … from person p1_0 where p1_0.age in (?,?,?)
findByCityIsNull()  ⇒ 返回 1 行 [Dee Ray]
    select … from person p1_0 where p1_0.city is null
findByActiveTrue()  ⇒ 返回 6 行 [Ann Lee, Cid Ray, Dee Ray, 50%off Sale, 500off Sale, Eve Poe]
    select … from person p1_0 where p1_0.active=true
findByCityNot("Rome")  ⇒ 返回 4 行 [Cid Ray, 50%off Sale, 500off Sale, Fay lee]
    select … from person p1_0 where p1_0.city<>?
findByCityOrderByAgeDesc("Oslo")  ⇒ 返回 4 行 [Fay lee, 500off Sale, 50%off Sale, Cid Ray]
    select … from person p1_0 where p1_0.city=? order by p1_0.age desc
findTop3ByOrderByAgeDesc()  ⇒ 返回 3 行 [Fay lee, 500off Sale, 50%off Sale]
    select … from person p1_0 order by p1_0.age desc limit ?
findFirstByOrderByAgeAsc()  ⇒ 返回 Dee Ray
    select … from person p1_0 order by p1_0.age limit ?
countByCity("Oslo")  ⇒ 返回 4
    select … from person p1_0 where p1_0.city=?
existsByLastName("Lee")  ⇒ 返回 true
    select … from person p1_0 where p1_0.last_name=? limit ?
findDistinctLastNameByCity("Oslo")  ⇒ 返回 抛 ConversionFailedException:Failed to convert from type [java.util.ArrayList<?>] to type [java.util.List<java.lang.String>] for value [[...]]
    select … from person p1_0 where p1_0.city=?
findOptionalByFirstName("Ann")  ⇒ 返回 Optional[Ann Lee]
    select … from person p1_0 where p1_0.first_name=?

前缀决定返回形态,By 之后按 And/Or/比较关键字拆成条件;Top 和 First 加 order by 与 limit。

字符串匹配

实测 · 本机执行输出
findByFirstNameLike("50%")(自己写通配符)  ⇒ 返回 2 行 [50%off Sale, 500off Sale]
    select … from person p1_0 where p1_0.first_name like ? escape '\\'
findByFirstNameStartingWith("50%")  ⇒ 返回 1 行 [50%off Sale]
    select … from person p1_0 where p1_0.first_name like ? escape '\\'
findByFirstNameContaining("0%o")  ⇒ 返回 1 行 [50%off Sale]
    select … from person p1_0 where p1_0.first_name like ? escape '\\'
findByLastNameIgnoreCase("LEE")  ⇒ 返回 3 行 [Ann Lee, Bob Lee, Fay lee]
    select … from person p1_0 where upper(p1_0.last_name)=upper(?)
findByLastName("LEE")(MySQL 默认排序规则)  ⇒ 返回 3 行 [Ann Lee, Bob Lee, Fay lee]
    select … from person p1_0 where p1_0.last_name=?

StartingWith 与 Containing 会转义 % 和 _;IgnoreCase 变成 upper();区分大小写取决于数据库。

嵌套属性

实测 · 本机执行输出
findByAddressZipCode("10001")  ⇒ 返回 3 行 [Ann Lee, Bob Lee, Eve Poe]
    select … from person p1_0 where p1_0.zip_code=?
findByAddress_ZipCode("10001")  ⇒ 返回 3 行 [Ann Lee, Bob Lee, Eve Poe]
    select … from person p1_0 where p1_0.zip_code=?

AddressZipCode 自动拆成 address.zipCode;有歧义时用下划线。

deleteBy

实测 · 本机执行输出
deleteByCity("Rome")(@Transactional)  ⇒ 返回 3
    select … from person p1_0 where p1_0.city=?
    delete from person where id=?
    delete from person where id=?
    delete from person where id=?

先 select 再逐行 delete,是 1 + N 条 SQL;要自己加 @Transactional。

写错方法名

实测 · 本机执行输出
  退出码 1;有 APPLICATION FAILED TO START 报告:False;输出共 71 行,其中堆栈帧 66 行
  异常链(外 → 里):
    org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'personRepo' defined in demo.PersonRepo defined in @EnableJpaRepositories declared on DataJpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: No property 'lastNme' found for type 'Person'; Did you mean 'l
    Caused by: org.springframework.data.repository.query.QueryCreationException: Cannot create query for method [PersonRepo.findByLastNme(java.lang.String)]; No property 'lastNme' found for type 'Person'; Did you mean 'lastName'
    Caused by: org.springframework.data.core.PropertyReferenceException: No property 'lastNme' found for type 'Person'; Did you mean 'lastName'
  nickname 也是错的,但报错里提到了它吗:False

应用启动时失败,只报第一个错误,没有整理过的报告。

分步演示

派生查询
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Hibernate 7、Spring Data JPA 随 spring-boot-starter-data-jpa;MySQL 是本机自己起的 26.7.0 实例)。实体 Person 8 行数据(其中包括名字 50%off 和 500off、姓 Lee 与 lee);SQL 来自 Hibernate 的 StatementInspector,select 的列清单在输出里被替换成 …;方法名写错的场景是另外编译的只含两个错方法的版本
    • 没有验证:返回 Page 和 Slice 的派生查询(见第 89 篇);投影接口与动态投影;关联实体上的派生条件(join);@Query 与命名查询;其他数据库(PostgreSQL)下 Containing 的转义写法;大小写敏感排序规则(utf8mb4_bin)下的行为;方法名过长时的可读性与性能,以及派生查询是否走索引(没有 explain)
    京ICP备2026067502号-1京公网安备11010502063487号