在 Spring Data JPA / Hibernate 项目中,我们经常需要实现不同复杂度的查询。本文从简单到复杂,梳理常用查询方案的特点、优缺点和适用场景,包括:@Query、Specification/CriteriaBuilder、Hibernate Criteria、QueryDSL、Query by Example(QBE)以及EntityManager.createNativeQuery。
1️⃣原生 SQL / JPQL (@Query)
- 特点:固定 SQL 或 JPQL,参数绑定可选(
:param)。 - 优点:
- 直观,SQL 性能可控。
- 对聚合、多表查询支持良好。
- 缺点:
- 动态条件写起来麻烦,需要大量 if / OR 条件或手写 SQL 拼接。
- 适合场景:
- 查询逻辑固定、复杂度不高,例如简单报表或固定列表查询。
示例:
@Query("SELECT u FROM User u WHERE u.status = :status AND u.department = :dept")List<User>findByStatusAndDepartment(@Param("status")UserStatusstatus,@Param("dept")Stringdepartment);2️⃣JpaSpecificationExecutor + Specification / CriteriaBuilder
- 特点:
- Specification 是基于CriteriaBuilder + CriteriaQuery + Predicate的封装。
- 提供标准 JPA 的动态查询能力。
- 优点:
- 动态条件拼接灵活。
- 类型安全,编译期检查字段。
- 支持分页、排序、简单聚合。
- 缺点:
- 聚合、CASE WHEN 或复杂多表查询写起来冗长。
- 语法比 QueryDSL 繁琐,可读性略低。
- 适合场景:
- 动态条件 + 单表或简单 JOIN + 分页查询。
- 需要标准 JPA API,无需额外依赖的项目。
示例(CriteriaBuilder 原生使用):
CriteriaBuildercb=entityManager.getCriteriaBuilder();CriteriaQuery<User>cq=cb.createQuery(User.class);Root<User>root=cq.from(User.class);List<Predicate>predicates=newArrayList<>();if(status!=null)predicates.add(cb.equal(root.get("status"),status));if(dept!=null)predicates.add(cb.equal(root.get("department"),dept));cq.select(root).where(cb.and(predicates.toArray(newPredicate[0])));List<User>users=entityManager.createQuery(cq).getResultList();示例(Specification 封装):
Specification<User>spec=(root,query,cb)->{List<Predicate>predicates=newArrayList<>();if(status!=null)predicates.add(cb.equal(root.get("status"),status));if(dept!=null)predicates.add(cb.equal(root.get("department"),dept));returncb.and(predicates.toArray(newPredicate[0]));};List<User>users=userRepository.findAll(spec,Sort.by("username"));3️⃣Hibernate Criteria / DetachedCriteria
- 特点:Hibernate 原生 Criteria API(老版本 JPA 也有类似接口)。
- 优点:
- 与 Specification 类似,可动态构建查询。
- 缺点:
- Hibernate 6 已不推荐使用,新项目建议用 CriteriaBuilder 或 QueryDSL。
- 适合场景:
- 历史项目或 Hibernate 原生项目。
示例:
Sessionsession=entityManager.unwrap(Session.class);Criteriacriteria=session.createCriteria(ExamInfo.class);if(status!=null)criteria.add(Restrictions.eq("status",status));List<ExamInfo>list=criteria.list();4️⃣QueryDSL(推荐复杂动态查询)
- 特点:
- 静态类型查询 DSL,完全类型安全。
- 支持动态条件、聚合、JOIN、排序。
- 优点:
- SQL 风格直观,类型安全,编译期检查。
- BooleanBuilder 拼接动态条件简单。
- 支持复杂聚合、CASE WHEN、多表 JOIN、分页排序。
- 缺点:
- 需要生成 Q 类(QueryDSL 注解处理器生成),学习成本稍高。
示例(复杂 SQL 转 QueryDSL):
QExamInfoei=QExamInfo.examInfo;QExamPaperetp=QExamPaper.examPaper;QExamParticipantep=QExamParticipant.examParticipant;JPAQuery<ExamInfoPageVo>query=newJPAQuery<>(entityManager);BooleanBuilderbuilder=newBooleanBuilder();if(param.getStatus()!=null)builder.and(ei.status.eq(param.getStatus()));if(StrUtil.isNotBlank(param.getExamName()))builder.and(ei.examName.like("%"+param.getExamName()+"%"));query.select(Projections.constructor(ExamInfoPageVo.class,ei.id,ei.examName,ei.status,ei.validStartTime,ei.validEndTime,ei.reviewDelayDays,ei.paper.id,etp.title,etp.duration,etp.score,ep.count(),ep.when(ep.required.eq(true),1).sum(),ep.when(ep.required.eq(false).and(ep.finalScore.isNull()),1).sum())).from(ei).leftJoin(ei.paper,etp).leftJoin(ei.participants,ep).where(builder).groupBy(ei.id,ei.examName,ei.status,ei.validStartTime,ei.validEndTime,ei.reviewDelayDays,ei.paper.id,etp.title,etp.duration,etp.score).fetch();其中 JPAQuery 可换成全局的 queryFactory
@Configuration public class QueryDslConfig { @PersistenceContext private EntityManager entityManager; @Bean public JPAQueryFactory jpaQueryFactory() { return new JPAQueryFactory(entityManager); } } 然后: @Resource private JPAQueryFactory queryFactory; queryFactory.select().fetch(); queryFactory.update().execute()5️⃣Spring Data JPA + Query by Example (QBE)
- 特点:根据实体示例生成查询。
- 优点:
- 快速生成简单查询,无需写 SQL / Specification。
- 类型安全,代码简洁。
- 缺点:
- 几乎不支持 JOIN、聚合和复杂 CASE WHEN。
- 动态条件能力有限,只能匹配实体非空属性。
- 适合场景:
- 简单动态条件查询、快速原型或后台管理系统。
示例:
Userprobe=newUser();probe.setStatus(UserStatus.ACTIVE);probe.setDepartment("IT");ExampleMatchermatcher=ExampleMatcher.matching().withIgnorePaths("id").withIgnoreCase("username").withStringMatcher(StringMatcher.CONTAINING);Example<User>example=Example.of(probe,matcher);List<User>users=userRepository.findAll(example);6️⃣EntityManager.createNativeQuery(原生 SQL)
- 特点:直接执行原生 SQL 查询或更新。
- 优点:
- 支持所有 SQL 特性(JOIN、聚合、CASE、函数)。
- 可调用数据库特定函数或存储过程。
- 性能可控,适合复杂查询或批量操作。
- 缺点:
- 不类型安全,运行时才报错。
- 需要手动映射结果到 DTO/VO。
- 适合场景:
- 报表查询、复杂聚合、多表操作。
- 数据库特性函数调用(JSON、存储过程等)。
- 批量更新/删除操作。
- 极端性能优化场景。
示例:
Queryquery=entityManager.createNativeQuery("SELECT d.dept_name, COUNT(u.id) AS user_count "+"FROM users u JOIN department d ON u.dept_id = d.id "+"GROUP BY d.dept_name");List<Object[]>results=query.getResultList();for(Object[]row:results){System.out.println("Dept: "+row[0]+", Count: "+row[1]);}7️⃣查询方式对比表
| 查询方式 | 动态条件 | 聚合/CASE WHEN | JOIN | 类型安全 | 可读性 | 适用场景 |
|---|---|---|---|---|---|---|
| @Query (JPQL/SQL) | ❌固定 | ✅ 支持 | ✅ 支持 | ❌ | 高 | 简单或固定查询、报表 |
| Specification / CriteriaBuilder | ✅ 完全动态 | 部分 | ✅ 支持简单 JOIN | ✅ | 中 | 动态条件、分页、简单聚合 |
| Hibernate Criteria | ✅ | 部分 | ✅ 支持 | ✅ | 中 | 历史项目或 Hibernate 原生项目 |
| QueryDSL | ✅ 完全动态 | ✅ 支持 | ✅ 支持 | ✅ | 高 | 复杂动态查询、多表聚合、报表 |
| QBE | ✅ 受限 | ❌ 不支持 | ❌ 不支持 | ✅ | 高 | 简单实体查询、快速原型 |
| createNativeQuery | ❌固定(可手动拼接) | ✅ 支持 | ✅ 支持 | ❌ | 中 | 报表、复杂聚合、多表操作、数据库函数、批量操作 |
8️⃣总结建议
- 固定查询、简单报表:
@Query最直接。 - 动态条件 + 分页 + 简单聚合:Specification / CriteriaBuilder(标准 JPA API)
- 复杂动态查询、多表 JOIN、聚合、报表类查询:QueryDSL 最适合。
- 快速简单动态查询(单表):QBE 方便,但企业项目使用不多。
- 依赖数据库特性、复杂 SQL、性能优化、批量操作:原生 SQL (
createNativeQuery) 最灵活。