邯郸市网站建设_网站建设公司_SSG_seo优化
2025/12/17 15:49:43 网站建设 项目流程

在 Spring Data JPA / Hibernate 项目中,我们经常需要实现不同复杂度的查询。本文从简单到复杂,梳理常用查询方案的特点、优缺点和适用场景,包括:@QuerySpecification/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 WHENJOIN类型安全可读性适用场景
@Query (JPQL/SQL)❌固定✅ 支持✅ 支持简单或固定查询、报表
Specification / CriteriaBuilder✅ 完全动态部分✅ 支持简单 JOIN动态条件、分页、简单聚合
Hibernate Criteria部分✅ 支持历史项目或 Hibernate 原生项目
QueryDSL✅ 完全动态✅ 支持✅ 支持复杂动态查询、多表聚合、报表
QBE✅ 受限❌ 不支持❌ 不支持简单实体查询、快速原型
createNativeQuery❌固定(可手动拼接)✅ 支持✅ 支持报表、复杂聚合、多表操作、数据库函数、批量操作

8️⃣总结建议

  1. 固定查询、简单报表@Query最直接。
  2. 动态条件 + 分页 + 简单聚合:Specification / CriteriaBuilder(标准 JPA API)
  3. 复杂动态查询、多表 JOIN、聚合、报表类查询:QueryDSL 最适合。
  4. 快速简单动态查询(单表):QBE 方便,但企业项目使用不多。
  5. 依赖数据库特性、复杂 SQL、性能优化、批量操作:原生 SQL (createNativeQuery) 最灵活。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询