在 spring data jpa 的查询世界里,开发者常常面临一个经典难题:面对层出不穷的数据访问需求,究竟该选用哪种查询方式?是依赖方法命名派生查询的极致简洁,还是拥抱 @query 的无限自由?是钟情于 specification 的类型安全与动态组合,还是青睐 query by example 的天然直观?
本文将逐一拆解这四种查询利器,从基础用法到高阶技巧(含投影、更新、子查询、动态条件拼接),并深入剖析分页与排序的统一实践,辅以大量实战案例,给出清晰的选择策略。无论你是刚入门的初学者,还是寻求进阶的开发者,都能在这里找到通往高效数据访问的钥匙。
一、方法命名派生查询(derived query methods)—— 最优雅的“零代码”方案
方法命名派生查询是 spring data jpa 最核心、最便捷的特性。你只需遵循一套约定好的命名规则,框架便会在运行时自动生成 jpql 查询语句,真正实现“不写一行 sql,也能查数据”。
1.1 命名结构解剖
一个派生查询方法由两部分组成,中间以 by 分隔:
- 引导词(introducer):定义操作类型,如 find、read、get、count、exists、delete 等。
- 条件部分(criteria):by 之后的部分,由属性名和关键字组合而成。
list<user> findbyname(string name);
─┬─ ──┬──
引导词 条件部分
| 引导词 | 含义 | 示例 |
|---|---|---|
| find…by | 返回匹配的实体或集合 | findbyname(string name) |
| read…by | 同 find | readbyname(string name) |
| get…by | 同 find | getbyname(string name) |
| count…by | 返回匹配结果的数量 | countbyname(string name) |
| exists…by | 判断是否存在匹配结果 | existsbyname(string name) |
| delete…by | 删除匹配的实体(先查后删) | deletebyname(string name) |
1.2 结果集控制:top / first / distinct
在引导词和 by 之间可插入 top、first 或 distinct,用于限制结果数量或去重:
// 年龄最大的前3条 list<user> findtop3byage(); // 年龄最大的第1条(等价于 top1) user findfirstbyage(); // 去重查询 list<user> finddistinctbylastnameandfirstname(string lastname, string firstname);
1.3 查询条件关键字大全
下表罗列了 spring data jpa 支持的所有查询关键字及其对应的 jpql 片段,掌握这些组合,便能覆盖绝大多数简单查询。
| 关键字 | 示例方法 | jpql 片段 |
|---|---|---|
| and | findbylastnameandfirstname | … where x.lastname = ?1 and x.firstname = ?2 |
| or | findbylastnameorfirstname | … where x.lastname = ?1 or x.firstname = ?2 |
| is, equals | findbyfirstname、findbyfirstnameis | … where x.firstname = ?1 |
| between | findbystartdatebetween | … where x.startdate between ?1 and ?2 |
| lessthan | findbyagelessthan | … where x.age < ?1 |
| lessthanequal | findbyagelessthanequal | … where x.age <= ?1 |
| greaterthan | findbyagegreaterthan | … where x.age > ?1 |
| greaterthanequal | findbyagegreaterthanequal | … where x.age >= ?1 |
| after | findbystartdateafter | … where x.startdate > ?1 |
| before | findbystartdatebefore | … where x.startdate < ?1 |
| isnull, null | findbyageisnull | … where x.age is null |
| isnotnull, notnull | findbyageisnotnull | … where x.age not null |
| like | findbyfirstnamelike | … where x.firstname like ?1 |
| notlike | findbyfirstnamenotlike | … where x.firstname not like ?1 |
| startingwith | findbyfirstnamestartingwith | … where x.firstname like ?1(参数追加 %) |
| endingwith | findbyfirstnameendingwith | … where x.firstname like ?1(参数前置 %) |
| containing | findbyfirstnamecontaining | … where x.firstname like ?1(参数前后加 %) |
| notcontaining | findbyfirstnamenotcontaining | … where x.firstname not like ?1(参数前后加 %) |
| in | findbyagein | … where x.age in ?1 |
| notin | findbyagenotin | … where x.age not in ?1 |
| true | findbyactivetrue | … where x.active = true |
| false | findbyactivefalse | … where x.active = false |
| ignorecase | findbyfirstnameignorecase | … where upper(x.firstname) = upper(?1) |
| orderby | findbyageorderbyagedesc | … order by x.age desc |
安全提示:对于 startingwith、endingwith、containing 等含 like 的关键字,spring data jpa 会自动对参数中的通配符(%、_)进行转义,防止注入攻击。
1.4 属性表达式与嵌套属性
条件部分可以引用嵌套属性,通过 . 或 _ 连接:
// 假设 user 有 address 类型的 address 属性,address 有 city 字段 list<user> findbyaddresscity(string city);
spring data jpa 会自动生成 join 查询。若属性名本身包含下划线,框架会优先识别为属性路径。
1.5 完整示例
@entity
@table(name = "users")
public class user {
@id @generatedvalue private integer id;
private string name;
private integer age;
private boolean active;
// getters/setters
}
public interface userrepository extends jparepository<user, integer> {
// 等值查询
list<user> findbyname(string name);
// 模糊匹配(包含)
list<user> findbynamecontaining(string infix);
// 组合条件 + 排序
list<user> findbynameandageorderbyagedesc(string name, int age);
// 计数与存在判断
long countbyname(string name);
boolean existsbyname(string name);
// 删除
void deletebyname(string name);
}
1.6 何时告别方法命名?
方法命名虽好,但当方法名变得冗长、需要复杂关联或数据库特有函数时,就该改用 @query 了。
二、@query 注解——突破命名约束的万 能 钥匙
@query 注解允许你在 repository 方法上直接编写查询语句,支持 jpql 和 原生 sql,是处理复杂查询的终极武器。
2.1 基础语法与参数绑定
① 基本用法
public interface userrepository extends jparepository<user, long> {
// jpql(操作实体)
@query("select u from user u where u.age > ?1")
list<user> findusersbyagegreaterthan(int age);
// 原生 sql(操作表)
@query(value = "select * from users where age > ?1", nativequery = true)
list<user> findusersbyagegreaterthannative(int age);
}
| 属性 | 说明 |
|---|---|
| value | 查询语句(jpql 或原生 sql) |
| nativequery | 是否为原生 sql,默认 false |
| countquery | 分页时用于计数的查询语句(重要!) |
| countprojection | 计数时使用的投影字段 |
② 参数绑定方式
- 位置参数(索引从 1 开始):
@query("select u from user u where u.name = ?1 and u.age = ?2")
user findbynameandage(string name, int age);
- 命名参数(推荐,可读性强):
@query("select u from user u where u.name = :name and u.age = :age")
user findbynameandage(@param("name") string name, @param("age") int age);
- 集合参数(自动展开 in 子句):
@query("select u from user u where u.age in :ages")
list<user> findbyagein(@param("ages") list<integer> ages);
2.2 高级查询技巧
① 多表关联(join)
// 隐式 join(通过属性路径)
@query("select u from user u where u.address.city = :city")
list<user> findbycity(@param("city") string city);
// 显式 join
@query("select u from user u join u.orders o where o.status = :status")
list<user> finduserswithorderstatus(@param("status") orderstatus status);
// 左外连接
@query("select u from user u left join u.orders o where o.total > :amount")
list<user> finduserswithlargeorders(@param("amount") bigdecimal amount);
② 子查询
// 查询年龄大于平均值的用户
@query("select u from user u where u.age > (select avg(age) from user)")
list<user> findusersolderthanaverage();
// exists 子查询
@query("select u from user u where exists (select 1 from order o where o.user = u and o.total > :amount)")
list<user> finduserswithorderabove(@param("amount") bigdecimal amount);
③ 投影(projection)—— 只查部分字段
- 接口投影(推荐):
public interface usernameandage {
string getname();
int getage();
}
@query("select u.name as name, u.age as age from user u where u.id = :id")
usernameandage findusernameandagebyid(@param("id") long id);
- dto 类投影(使用构造器表达式):
public class userdto {
private string name;
private int age;
public userdto(string name, int age) { this.name = name; this.age = age; }
// getters
}
@query("select new com.example.dto.userdto(u.name, u.age) from user u where u.id = :id")
userdto finduserdtobyid(@param("id") long id);
④ 集合表达式与动态 in
@query("select u from user u where u.name in :names")
list<user> findbynamein(@param("names") collection<string> names);
传入空集合时可能产生异常,建议调用前做非空校验。
⑤ 使用 spel 表达式
// 引用实体名(多租户/动态表名)
@query("select u from #{#entityname} u where u.name = :name")
list<user> findbyentityname(@param("name") string name);
// 动态排序(慎用于原生sql)
@query("select u from user u order by #{#sortfield} #{#sortdirection}")
list<user> findallsorted(@param("sortfield") string field, @param("sortdirection") string direction);
2.3 更新与删除(@modifying)
@query 可执行 update/delete,但必须与 @modifying 联用。
① 基础用法
@modifying
@query("update user u set u.active = false where u.lastlogindate < :cutoff")
int deactivateinactiveusers(@param("cutoff") localdatetime cutoff);
返回值为受影响行数。
② 清除持久化上下文
更新后一级缓存可能残留旧数据,可通过 clearautomatically 自动清理:
@modifying(clearautomatically = true)
@query("update user u set u.active = false where u.id = :id")
int deactivateuser(@param("id") long id);
若需要立即刷新缓存,可同时设置 flushautomatically = true。
③ 事务要求
@modifying 方法必须在事务中执行,通常在 service 层用 @transactional 包裹。
2.4 原生 sql 的特殊处理
- 结果映射:默认返回
object[],若映射为实体,需确保查询字段与实体字段一致。 - 分页:必须同时指定
countquery,否则分页可能失效(详见第五章分页专项)。 - 命名参数:支持
@param,写法与 jpql 相同。
2.5 实战案例集锦
案例 1:可选参数(coalesce 模拟动态条件)
@query("select u from user u where (:name is null or u.name = :name) and (:age is null or u.age = :age)")
list<user> searchusers(@param("name") string name, @param("age") integer age);
大数据量下慎用,建议改用
specification。
案例 2:计算字段 + 排序
@query("select u, (u.age - (select avg(age) from user)) as agediff from user u where u.active = true order by agediff desc")
list<object[]> finduserswithagedifference();
案例 3:批量更新状态
@modifying(clearautomatically = true)
@query("update order o set o.status = :newstatus where o.status = :oldstatus and o.createddate < :cutoff")
int batchupdateorderstatus(@param("newstatus") orderstatus newstatus,
@param("oldstatus") orderstatus oldstatus,
@param("cutoff") localdatetime cutoff);
案例 4:调用数据库函数(如 date)
@query("select o from order o where function('date', o.createddate) = current_date")
list<order> findtodayorders();
案例 5:分组聚合
@query("select u.department, count(u), avg(u.salary) from user u group by u.department having avg(u.salary) > :avg")
list<object[]> getdepartmentstats(@param("avg") double avg);
2.6 调试与最佳实践
- sql 日志:在
application.properties开启:
spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true logging.level.org.hibernate.sql=debug logging.level.org.hibernate.type.descriptor.sql.basicbinder=trace
- jpql vs 原生 sql:优先 jpql(数据库无关、自动映射),原生 sql 仅在 jpql 无法实现时使用。
- 性能优化:避免
select *,使用投影;为分页查询测试countquery效率。
三、specification——类型安全的动态查询工厂
当查询条件动态变化(如多字段组合搜索),且希望编译期类型安全时,specification 是最佳搭档。它基于 jpa 2.0 的 criteria api,将查询条件的构建以规范(specification)对象的形式封装,并可自由组合。
3.1 启用 specification
让 repository 同时继承 jpaspecificationexecutor(jparepository 已包含):
public interface userrepository extends jparepository<user, long>,
jpaspecificationexecutor<user> {
}
jpaspecificationexecutor 提供了 findone、findall、count、exists 等方法,均接受 specification 参数,且天然支持分页(传入 pageable)。
3.2 编写与使用 specification
specification 是一个函数式接口,只需实现 topredicate 方法:
@functionalinterface
public interface specification<t> {
predicate topredicate(root<t> root, criteriaquery<?> query, criteriabuilder builder);
}
示例:查询年龄大于 18 的用户。
specification<user> agegreaterthan18 = (root, query, builder) ->
builder.greaterthan(root.get("age"), 18);
list<user> users = userrepository.findall(agegreaterthan18);
多条件组合:
specification<user> spec = (root, query, builder) -> {
predicate agebetween = builder.between(root.get("age"), 18, 30);
predicate isactive = builder.istrue(root.get("active"));
return builder.and(agebetween, isactive);
};
list<user> users = userrepository.findall(spec);
3.3 复用与组合
将常用规范抽取为静态方法:
public class userspecs {
public static specification<user> agegreaterthan(int age) {
return (root, query, builder) -> builder.greaterthan(root.get("age"), age);
}
public static specification<user> isactive() {
return (root, query, builder) -> builder.istrue(root.get("active"));
}
public static specification<user> namecontains(string keyword) {
return (root, query, builder) ->
builder.like(root.get("name"), "%" + keyword + "%");
}
}
组合使用(and / or):
list<user> users = userrepository.findall(
specification.where(userspecs.agegreaterthan(18))
.and(userspecs.isactive())
.and(userspecs.namecontains("张"))
);
3.4 结合 jpa 静态元模型(终极类型安全)
使用字符串 "age" 存在字段改名导致运行时错误的风险。通过 jpa 静态元模型(如 hibernate jpamodelgen 生成 user_ 类),可获得编译期检查:
specification<user> agegreaterthan18 = (root, query, builder) ->
builder.greaterthan(root.get(user_.age), 18); // 编译期安全!
启用方式:在 maven/gradle 中添加 org.hibernate:hibernate-jpamodelgen 注解处理器,编译后自动生成 user_ 类。
3.5 specification 的适用场景
- 后台管理系统多条件筛选(如用户列表搜索)
- 动态报表查询
- 需要复用和组合查询条件的场景
- 执行基于条件的删除(
delete(specification))
四、query by example(qbe)—— 用样本对象驱动查询
按示例查询(qbe)提供了一种极简的动态查询方式:你只需填充一个样本实体对象,框架便会自动以其非空字段为条件生成查询,无需编写任何字段名。
4.1 核心概念
- probe(探针):填充了属性值的领域对象实例。
- examplematcher(匹配器):定义字段匹配规则(如字符串匹配模式、忽略大小写等)。
- example:probe + examplematcher 的组合。
4.2 基本用法
// 创建探针(null 属性默认忽略)
user probe = new user();
probe.setname("张三");
probe.setage(25);
example<user> example = example.of(probe);
list<user> results = userrepository.findall(example);
默认行为:精确匹配所有非空字段,字符串采用数据库默认匹配方式(通常为 =)。
4.3 examplematcher:精细控制匹配规则
examplematcher matcher = examplematcher.matching()
.withignorepaths("id", "createdat") // 忽略这些字段
.withstringmatcher(stringmatcher.containing) // 全局包含匹配
.withignorecase() // 全局忽略大小写
.withmatcher("email", match -> match.endswith())
.withmatcher("name", match -> match.startswith().ignorecase());
example<user> example = example.of(probe, matcher);
list<user> results = userrepository.findall(example);
字符串匹配策略:
| 策略 | 说明 | sql 效果 |
|---|---|---|
default | 存储特定默认值(通常精确匹配) | = ? |
exact | 精确匹配 | = ? |
starting | 前缀匹配 | like ?% |
ending | 后缀匹配 | like %? |
containing | 包含匹配 | like %?% |
4.4 fetchablefluentquery(流式查询)
自 spring data jpa 2.5 起,支持链式调用进行排序、分页和投影:
user probe = new user();
probe.setactive(true);
example<user> example = example.of(probe);
list<user> results = userrepository.findby(example,
query -> query
.sortby(sort.by("age").descending())
.page(pagerequest.of(0, 10))
.stream()
.collect(collectors.tolist())
);
4.5 qbe 的适用场景与局限性
适用场景:
- 查询条件动态变化,且不想编写复杂代码
- 领域对象频繁重构,希望减少字段变更带来的影响
- 快速原型开发
局限性:
- 不支持嵌套或分组条件(如
(a=1 and b=2) or (c=3)) - 同一字段只能有一个过滤值
- 字符串匹配能力受数据库限制
- 不支持集合类型属性的匹配
五、分页与排序全攻略:四种方式统一实践
分页是几乎所有业务系统都绕不开的需求。在 spring data jpa 中,无论你使用上述哪一种查询方式,分页机制都是高度统一的——核心依赖 pageable 和 page / slice 接口。下面我们先了解基础,再逐一展示四种方式如何具体落地。
5.1 分页核心概念
- pageable:分页请求对象,封装了页码、每页大小、排序信息。
- pagerequest:
pageable的常用实现类,通过pagerequest.of(page, size, sort)创建。 - page:分页结果对象,包含数据列表、总记录数、总页数等完整分页信息(会额外执行 count 查询)。
- slice:分页结果切片,仅知道是否有下一页,不执行 count 查询,性能优于
page,适用于无限滚动等场景。
// 创建分页请求:第0页,每页10条,按年龄降序
pageable pageable = pagerequest.of(0, 10, sort.by("age").descending());
// 多字段排序
pageable pageable = pagerequest.of(0, 10, sort.by("age").descending().and(sort.by("id").ascending()));
5.2 方式一:方法命名派生查询 + 分页
派生查询方法只需在参数列表中增加 pageable 参数,返回值改为 page 或 slice,框架便会自动解析并生成分页 sql。
public interface userrepository extends jparepository<user, long> {
// 返回 page(含总记录数)
page<user> findbynamecontaining(string keyword, pageable pageable);
// 返回 slice(不含总记录数,性能更高)
slice<user> findbyagegreaterthan(int age, pageable pageable);
// 结合排序(pageable 中已包含排序,无需单独加 orderby)
page<user> findbyactivetrue(pageable pageable);
}
调用示例:
// 查询姓名包含"张"的用户,按年龄降序分页
pageable pageable = pagerequest.of(0, 10, sort.by(sort.direction.desc, "age"));
page<user> page = userrepository.findbynamecontaining("张", pageable);
system.out.println("总记录数:" + page.gettotalelements());
system.out.println("总页数:" + page.gettotalpages());
system.out.println("当前页数据:" + page.getcontent());
重要:方法命名派生查询中,如果方法名里已经带了 orderby(如 findbynameorderbyagedesc),再传入带排序的 pageable,排序会叠加,可能导致意外结果。建议仅在 pageable 中指定排序,保持方法名干净。
5.3 方式二:@query 注解 + 分页
@query 对分页的支持极为灵活,jpql 和原生 sql 都适用。
① jpql 标准分页
直接在方法参数中加入 pageable,无需在 jpql 中写 offset / limit,框架会自动拼接:
@query("select u from user u where u.age > :age")
page<user> findbyagegreaterthan(@param("age") int age, pageable pageable);
② 自定义 count 查询(性能优化关键)
当 jpql 查询包含多表 join 或复杂条件时,自动生成的 count 查询可能效率低下,甚至因语法问题报错。此时应手动指定 countquery:
@query(value = "select u from user u left join u.orders o where o.total > :amount",
countquery = "select count(u) from user u where exists (select 1 from order o where o.user = u and o.total > :amount)")
page<user> finduserswithlargeorders(@param("amount") bigdecimal amount, pageable pageable);
③ 原生 sql 分页(必须指定 countquery)
原生 sql 的分页语句因数据库方言而异(如 mysql 的 limit、oracle 的 rownum),spring data jpa 会根据方言自动拼接,但 count 查询必须手工提供,否则分页会失效:
@query(value = "select * from users where age > ?1 order by id",
countquery = "select count(*) from users where age > ?1",
nativequery = true)
page<user> findusersbyagegreaterthannative(int age, pageable pageable);
注意:原生 sql 的 order by 必须写在主查询中,且如果 pageable 中带有 sort,二者可能冲突,建议统一在 pageable 中控制排序,主查询中不写 order by。
5.4 方式三:specification + 分页
specification 与 pageable 是天生的搭档,通过 jpaspecificationexecutor 提供的方法可以直接传入分页参数:
public interface userrepository extends jparepository<user, long>,
jpaspecificationexecutor<user> {
// 无需额外定义方法,父接口已提供
}
调用示例:
// 构建动态条件
specification<user> spec = (root, query, builder) -> {
predicate agepredicate = builder.between(root.get("age"), 18, 30);
predicate activepredicate = builder.istrue(root.get("active"));
return builder.and(agepredicate, activepredicate);
};
// 分页 + 排序
pageable pageable = pagerequest.of(0, 10, sort.by("age").descending());
page<user> page = userrepository.findall(spec, pageable);
结合静态元模型(类型安全):
specification<user> spec = (root, query, builder) ->
builder.greaterthan(root.get(user_.age), 18);
page<user> page = userrepository.findall(spec, pagerequest.of(0, 10, sort.by(user_.age).descending()));
5.5 方式四:query by example(qbe)+ 分页
querybyexampleexecutor 同样原生支持分页,使用方式与 specification 极其相似:
public interface userrepository extends jparepository<user, long> {
// 继承自 querybyexampleexecutor,无需额外定义
}
传统分页写法:
// 构建探针
user probe = new user();
probe.setname("张");
probe.setactive(true);
// 构建匹配器
examplematcher matcher = examplematcher.matching()
.withstringmatcher(stringmatcher.containing)
.withignorepaths("id", "createdat");
example<user> example = example.of(probe, matcher);
// 分页查询
pageable pageable = pagerequest.of(0, 10, sort.by("age").ascending());
page<user> page = userrepository.findall(example, pageable);
流式 api(fetchablefluentquery)分页(spring data jpa 2.5+):
// 获取 list
list<user> results = userrepository.findby(example,
query -> query
.sortby(sort.by("age").descending())
.page(pagerequest.of(0, 10))
.stream()
.collect(collectors.tolist())
);
// 若需要完整 page 对象
page<user> page = userrepository.findby(example,
query -> query
.sortby(sort.by("age").descending())
.page(pagerequest.of(0, 10))
);
5.6 page 与 slice 的选择策略
| 特性 | page | slice |
|---|---|---|
| 是否执行 count 查询 | ✅ 是(消耗性能) | ❌ 否(性能高) |
| 能否获取总记录数 | ✅ 可以 | ❌ 不可以 |
| 能否获取总页数 | ✅ 可以 | ❌ 不可以 |
| 适用场景 | 后台管理系统、需要显示总条数的表格 | 移动端列表、无限滚动、api 网关透传 |
5.7 分页最佳实践
- 统一封装分页请求:在 controller 层接收 page、size、sort 参数,转换为 pageable 对象,避免在 service 层硬编码。
- 避免超大页码:对 page 参数做合理性校验,防止恶意请求导致内存溢出。
- count 查询优化:对于复杂 join 查询,务必测试自动生成的 count sql,必要时手动指定 countquery。
- 排序字段白名单:若排序字段由前端传入,请做白名单校验,防止 sql 注入(尤其是原生 sql 场景)。
- 善用 sort.by 静态工厂:
// 安全且优雅的排序构建
sort sort = sort.by("age").descending()
.and(sort.by("id").ascending());
六、四大查询方式终极对比与选择策略
现在,我们在之前的对比表中增加“分页支持”这一关键维度:
| 对比维度 | 方法命名派生 | @query | specification | qbe |
|---|---|---|---|---|
| 代码量 | ⭐⭐⭐⭐⭐ 最少 | ⭐⭐⭐ 中等 | ⭐⭐ 较多 | ⭐⭐⭐⭐ 较少 |
| 可读性 | 方法名即文档 | 需阅读 jpql/sql | 需理解 criteria api | 需理解匹配器配置 |
| 动态性 | ❌ 固定条件 | ❌ 固定条件(除非拼装字符串) | ✅ 天然动态 | ✅ 天然动态 |
| 类型安全 | ⚠️ 无编译检查 | ⚠️ 无编译检查 | ✅ 配合元模型可编译检查 | ⚠️ 无编译检查 |
| 灵活性 | 受关键字限制 | 最高(任意 jpql/sql) | 高(编程构建) | 受匹配器限制 |
| 复杂查询能力 | 弱(简单条件) | 极强(任意复杂度) | 强(可构建复杂条件) | 弱(单层简单条件) |
| 分页支持 | ✅ 原生支持 | ✅ 原生支持(可自定义 count) | ✅ 原生支持 | ✅ 原生支持 |
| 重构友好 | 需同步改方法名 | 需同步改 jpql | 配合元模型自动感知 | 修改 setter 调用即可 |
尽管四种方式都支持分页,但 @query 在复杂分页场景(如多表 join 的 count 优化)中拥有最强的控制力;而方法命名派生和 qbe 在简单分页场景中则更加便捷。
分页场景选择速查表
| 分页场景 | 推荐方式 | 理由 |
|---|---|---|
| 单表简单条件分页(如根据名称模糊查询) | 方法命名派生 | 代码极简,无需额外 sql |
| 单表动态多条件分页(如后台搜索表单) | specification | 条件灵活组合,类型安全 |
| 多表关联分页(如用户+订单联合查询) | @query + 自定义 count | 可精准控制 join 和 count 查询,避免性能陷阱 |
| 动态条件且不想写复杂代码的原型开发 | qbe | 以样本对象驱动,上手快 |
| 需要调用数据库特有函数的分页(如全文检索) | @query(原生 sql) | 可编写数据库专属 sql,不受 jpql 限制 |
协同使用建议
在实际项目中,这四种方式并非互斥,而是可以共存于同一个 repository 中。例如:
public interface userrepository extends jparepository<user, long>,
jpaspecificationexecutor<user> {
// 简单查询 → 派生
list<user> findbyname(string name);
// 复杂关联 → @query
@query("select u from user u join u.orders o where o.total > :amount")
list<user> finduserswithordertotalgreaterthan(@param("amount") bigdecimal amount);
// 动态多条件 → specification(由外部调用)
// 由 service 层构建 specification 传入 findall(specification)
// 快速原型 → qbe(由外部调用)
// 由 service 层构建 example 传入 findall(example)
}
七、结语
spring data jpa 的四种查询方式——方法命名派生、@query、specification 和 query by example——各有侧重,互为补充。方法命名派生用最少的代码搞定最常见的查询;@query 让你在复杂场景下掌控全局,并精细优化分页性能;specification 则以类型安全的方式编织动态条件;qbe 则用最自然的方式表达动态查询。
在分页能力上,它们殊途同归,均通过统一的 pageable 机制实现了优雅的支持。选择何种方式,应综合考量查询复杂度、动态性需求、类型安全要求和性能敏感度:
- 简单固定条件 → 派生查询
- 复杂固定逻辑 → @query
- 动态组合条件 → specification
- 快速动态样本 → qbe
掌握这四种武器,并根据实际场景灵活选择,你便能在 spring data jpa 的查询世界里游刃有余,既写出简洁优雅的代码,又能应对层出不穷的业务变化。希望这份涵盖了分页细节与进阶特性的完整指南,能切实帮助你在实际项目中做出最优决策,高效、高质量地完成数据访问层的构建。
到此这篇关于spring data jpa 查询的四种方法的文章就介绍到这了,更多相关spring data jpa 查询内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论