1. 从零开始认识Query注解
Spring Data JPA的@Query
注解就像是为JPA定制的GPS导航器。假设你正在开发电商平台订单管理系统,常规的findByXxx
方法已经无法满足复杂的分页检索需求。此时@Query
注解就派上了用场。
下面这个典型示例展示了如何查询最近七天内的订单数据:
@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
// 使用JPQL查询指定时间范围内的订单
@Query("SELECT o FROM Order o WHERE o.createTime BETWEEN :start AND :end")
List<Order> findRecentOrders(@Param("start") LocalDateTime startTime,
@Param("end") LocalDateTime endTime);
// 强制刷新缓存的最新订单查询
@Query(value = "SELECT * FROM orders WHERE status = 'PAID'",
nativeQuery = true)
@Modifying(flushAutomatically = true)
List<Order> findPaidOrdersNative();
}
这段代码充分展示了两种查询方式的差异:
- 上半部分使用JPQL进行类型安全的对象查询
- 下半部分通过原生SQL直接操作数据库表结构
2. 动态条件查询的终极形态
2.1 JPA Criteria API实践
当遇到包含5-10个可筛选条件的商品查询页面时,硬编码的查询语句会变成开发噩梦。来看这个基于Criteria API实现动态查询的示例:
public class ProductSpecification implements Specification<Product> {
private ProductQueryCriteria criteria;
@Override
public Predicate toPredicate(Root<Product> root,
CriteriaQuery<?> query,
CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
// 价格区间筛选
if(criteria.getMinPrice() != null && criteria.getMaxPrice() != null) {
predicates.add(builder.between(root.get("price"),
criteria.getMinPrice(), criteria.getMaxPrice()));
}
// 关键字全文检索
if(StringUtils.hasText(criteria.getKeyword())) {
predicates.add(builder.or(
builder.like(root.get("name"), "%" + criteria.getKeyword() + "%"),
builder.like(root.get("description"), "%" + criteria.getKeyword() + "%")
));
}
return query.where(predicates.toArray(new Predicate[0]))
.orderBy(builder.desc(root.get("createTime")))
.getRestriction();
}
}
2.2 QueryDSL的动态之美
基于QueryDSL的查询实现更加直观:
public List<Product> searchProducts(ProductQueryParam param) {
QProduct product = QProduct.product;
BooleanBuilder builder = new BooleanBuilder();
// 分类过滤
if(param.getCategoryId() != null) {
builder.and(product.category.id.eq(param.getCategoryId()));
}
// 库存状态
if(param.isInStock()) {
builder.and(product.stock.gt(0));
}
// 排序组合
return queryFactory.selectFrom(product)
.where(builder)
.orderBy(product.sales.desc(),
product.createTime.asc())
.fetch();
}
这种链式语法能直观反应查询逻辑的组成方式
3. 实用场景分析
3.1 分页组合查询
在处理用户管理后台的分页列表时,典型查询需求包含:
- 时间范围筛选
- 状态过滤
- 关键字搜索
- 多重排序
@Query(value = "SELECT u FROM User u WHERE "
+ "(u.registerTime BETWEEN :start AND :end) "
+ "AND (:status IS NULL OR u.status = :status) "
+ "AND (u.username LIKE %:keyword% OR u.email LIKE %:keyword%)",
countQuery = "SELECT count(u) FROM User u WHERE ...")
Page<User> searchUsers(@Param("start") Date start,
@Param("end") Date end,
@Param("status") UserStatus status,
@Param("keyword") String keyword,
Pageable pageable);
这里使用了countQuery参数确保分页统计的准确性
3.2 统计聚合场景
商品销量统计报表的典型实现:
public interface SalesReportRepository extends JpaRepository<Order, Long> {
// 按分类分组统计销售数据
@Query("SELECT new com.example.SalesSummary(c.name, SUM(o.amount)) "
+ "FROM Order o JOIN o.product p JOIN p.category c "
+ "WHERE o.status = 'COMPLETED' "
+ "GROUP BY c.id")
List<SalesSummary> getCategorySales();
}
这里使用了构造函数表达式将查询结果直接转换为DTO对象
4. 技术选型指南
4.1 @Query注解的优缺点
优势特征:
- 精确控制查询语句
- 支持DTO投影
- 编译时SQL校验
- 参数灵活绑定
局限之处:
- 动态条件拼接困难
- 复杂查询可读性下降
- 更新维护成本较高
4.2 动态查询方案对比
维度 | Criteria API | QueryDSL | Specification |
---|---|---|---|
类型安全 | ★★★★☆ | ★★★★★ | ★★★★☆ |
学习曲线 | 较陡峭 | 中等 | 平缓 |
调试友好度 | 一般 | 优秀 | 中等 |
功能扩展性 | 原生支持 | 依赖第三方 | Spring整合 |
5. 专家级注意事项
- N+1问题防范:
在关联查询中,始终通过
JOIN FETCH
预先加载必要数据:
@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.department = :dept")
List<User> findUsersWithRoles(@Param("dept") Department department);
- 参数绑定禁区: 绝对禁止拼接未校验的字符串参数:
// 错误示范:存在SQL注入风险
@Query("SELECT * FROM users WHERE name = '"+ "#{name}" + "'")
List<User> searchByName(@Param("name") String name);
- 性能优化手段: 对大数据量查询采用流式处理:
@QueryHints(value = @QueryHint(name = HINT_FETCH_SIZE, value = "100"))
@Query("SELECT u FROM User u WHERE u.status = :status")
Stream<User> streamActiveUsers(@Param("status") UserStatus status);
6. 架构师总结要点
通过对@Query
注解的深入挖掘和动态查询技术的灵活搭配,我们找到了解决复杂业务查询的金钥匙。在实际项目开发中:
- 简单查询优先使用Spring Data JPA的声明式方法
- 中等复杂度业务选择
@Query
与JPQL组合 - 动态查询场景采用QueryDSL或Specification模式
- 性能关键操作考虑原生SQL优化
最终的方案选择应该像挑选瑞士军刀一样,根据具体的业务场景的切割需求,选择最合适的工具组合。