如何使用Spring Data JPA Specifications创建自定义查询?
多表关联下用JPA Specification构建动态WHERE子句
1. 先做好实体关联映射
确保实体类之间的关联关系配置正确,比如用@ManyToOne、@OneToMany标注,按需设置fetch类型(LAZY/EAGER)。举个多表关联的例子:
@Entity public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private LocalDateTime orderTime; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "product_id") private Product product; // getter/setter } @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String email; // getter/setter } @Entity public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String productName; private BigDecimal price; // getter/setter }
2. 用Specification实现多表动态条件
Specification的核心是通过Root、CriteriaBuilder构建关联和动态Predicate条件,完全适配多表场景:
2.1 定义查询参数DTO
先把前端传入的搜索参数封装成DTO:
public class OrderQueryDTO { private String username; private BigDecimal minPrice; private BigDecimal maxPrice; private LocalDateTime startOrderTime; private LocalDateTime endOrderTime; // getter/setter }
2.2 编写Specification逻辑
在toPredicate方法里关联表,并根据参数动态拼接条件:
public class OrderSpecification implements Specification<Order> { private final OrderQueryDTO queryDTO; public OrderSpecification(OrderQueryDTO queryDTO) { this.queryDTO = queryDTO; } @Override public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder cb) { List<Predicate> predicates = new ArrayList<>(); // 关联User表,模糊匹配用户名(参数非空才添加条件) if (StringUtils.hasText(queryDTO.getUsername())) { Join<Order, User> userJoin = root.join("user", JoinType.INNER); predicates.add(cb.like(userJoin.get("username"), "%" + queryDTO.getUsername() + "%")); } // 关联Product表,筛选价格范围 if (queryDTO.getMinPrice() != null) { Join<Order, Product> productJoin = root.join("product", JoinType.INNER); predicates.add(cb.greaterThanOrEqualTo(productJoin.get("price"), queryDTO.getMinPrice())); } if (queryDTO.getMaxPrice() != null) { Join<Order, Product> productJoin = root.join("product", JoinType.INNER); predicates.add(cb.lessThanOrEqualTo(productJoin.get("price"), queryDTO.getMaxPrice())); } // 订单时间范围筛选 if (queryDTO.getStartOrderTime() != null) { predicates.add(cb.greaterThanOrEqualTo(root.get("orderTime"), queryDTO.getStartOrderTime())); } if (queryDTO.getEndOrderTime() != null) { predicates.add(cb.lessThanOrEqualTo(root.get("orderTime"), queryDTO.getEndOrderTime())); } // 所有条件用AND拼接 return cb.and(predicates.toArray(new Predicate[0])); } }
2.3 仓库层调用
让你的Repository继承JpaSpecificationExecutor:
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> { }
在Service层直接使用:
@Service public class OrderService { @Autowired private OrderRepository orderRepository; public List<Order> searchOrders(OrderQueryDTO queryDTO) { Specification<Order> spec = new OrderSpecification(queryDTO); return orderRepository.findAll(spec); } }
3. 若需结合@Query实现动态条件
如果偏好使用@Query,可以通过SpEL表达式处理空参数,适合参数固定的场景:
@Query("SELECT o FROM Order o " + "JOIN o.user u " + "JOIN o.product p " + "WHERE (:username IS NULL OR u.username LIKE %:username%) " + "AND (:minPrice IS NULL OR p.price >= :minPrice) " + "AND (:maxPrice IS NULL OR p.price <= :maxPrice)") List<Order> searchOrders(@Param("username") String username, @Param("minPrice") BigDecimal minPrice, @Param("maxPrice") BigDecimal maxPrice);
注意事项
- 关联查询时可通过
root.fetch()避免N+1问题,比如root.fetch("user", JoinType.INNER)一次性加载关联数据 - 复杂逻辑可以用
cb.or()组合条件,灵活控制逻辑关系 - 分页需求直接调用
orderRepository.findAll(spec, pageable)即可
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

