You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot如何结合原生查询使用Criteria Builder或JPA Specifications做后端过滤?

结合Spring Boot实现PrimeNG表格懒加载的后端数据过滤方案

针对你用Angular+PrimeNG懒加载表格、后端已有原生查询的场景,下面提供几种实用的实现方式,适配不同的查询复杂度:

方案1:JPA Specifications(推荐,适配简单到中等复杂度查询)

Specifications可以动态构建JPQL查询条件,完美整合Spring Data JPA的分页、排序能力,和PrimeNG的懒加载参数天然匹配。

步骤1:定义Specification过滤逻辑

假设你的实体是User,针对前端传来的过滤字段封装动态条件:

public class UserSpecs {
    public static Specification<User> withFilters(Map<String, String> filters) {
        return (root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            
            // 遍历前端过滤条件,按需添加匹配规则
            filters.forEach((field, value) -> {
                switch(field) {
                    case "username":
                        predicates.add(cb.like(cb.lower(root.get("username")), "%" + value.toLowerCase() + "%"));
                        break;
                    case "status":
                        predicates.add(cb.equal(root.get("status"), value));
                        break;
                    case "createTime":
                        // 日期范围过滤示例(假设前端传的是yyyy-MM-dd格式)
                        LocalDate date = LocalDate.parse(value);
                        predicates.add(cb.equal(cb.function("DATE", LocalDate.class, root.get("createTime")), date));
                        break;
                    // 其他字段的过滤规则按需添加
                }
            });
            
            return cb.and(predicates.toArray(new Predicate[0]));
        };
    }
}

步骤2:扩展Repository接口

让你的Repository继承JpaSpecificationExecutor,获得动态查询能力:

public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
    // 保留你已有的原生查询方法,比如:
    // @Query(nativeQuery = true, value = "SELECT * FROM users WHERE department_id = ?1")
    // List<User> findByDepartmentId(Long deptId);
}

步骤3:Service层处理懒加载请求

接收PrimeNG传来的分页、排序、过滤参数,组装查询并返回分页结果:

@Service
public class UserService {
    @Autowired
    private UserRepository userRepo;

    public Page<User> getLazyUserData(LazyLoadRequest request) {
        // 构建过滤条件
        Specification<User> spec = UserSpecs.withFilters(request.getFilters());
        
        // 处理排序
        Sort sort = Sort.by(request.getSortField());
        if (request.getSortOrder() == -1) { // PrimeNG中sortOrder=-1表示降序
            sort = sort.descending();
        } else {
            sort = sort.ascending();
        }
        
        // 组装分页参数
        Pageable pageable = PageRequest.of(request.getPage(), request.getSize(), sort);
        
        // 执行查询
        return userRepo.findAll(spec, pageable);
    }
}

方案2:动态拼接原生SQL(适配复杂多表关联的原生查询)

如果你的现有原生查询是复杂多表关联,无法转为JPQL,可以通过动态拼接WHERE条件实现过滤,同时做好SQL注入防护。

步骤1:定义基础原生SQL

private static final String BASE_SQL = "SELECT u.id, u.username, u.status, d.dept_name " +
                                      "FROM users u " +
                                      "JOIN departments d ON u.dept_id = d.id";

步骤2:动态构建查询语句

@Service
public class UserService {
    @Autowired
    private EntityManager em;

    public Page<UserDTO> getLazyUserDataWithNativeQuery(LazyLoadRequest request) {
        StringBuilder sqlBuilder = new StringBuilder(BASE_SQL);
        List<Object> params = new ArrayList<>();
        
        // 添加过滤条件
        if (!request.getFilters().isEmpty()) {
            sqlBuilder.append(" WHERE 1=1");
            request.getFilters().forEach((field, value) -> {
                switch(field) {
                    case "username":
                        sqlBuilder.append(" AND LOWER(u.username) LIKE ?");
                        params.add("%" + value.toLowerCase() + "%");
                        break;
                    case "deptName":
                        sqlBuilder.append(" AND LOWER(d.dept_name) LIKE ?");
                        params.add("%" + value.toLowerCase() + "%");
                        break;
                    case "status":
                        sqlBuilder.append(" AND u.status = ?");
                        params.add(value);
                        break;
                }
            });
        }
        
        // 添加排序
        if (request.getSortField() != null) {
            sqlBuilder.append(" ORDER BY ").append(request.getSortField());
            sqlBuilder.append(request.getSortOrder() == -1 ? " DESC" : " ASC");
        }
        
        // 统计总条数
        String countSql = "SELECT COUNT(*) FROM (" + BASE_SQL + 
                          (sqlBuilder.indexOf("WHERE") != -1 ? sqlBuilder.substring(sqlBuilder.indexOf("WHERE")) : "") + 
                          ") AS temp";
        Query countQuery = em.createNativeQuery(countSql);
        for (int i = 0; i < params.size(); i++) {
            countQuery.setParameter(i + 1, params.get(i));
        }
        long total = ((Number) countQuery.getSingleResult()).longValue();
        
        // 查询分页数据
        Query dataQuery = em.createNativeQuery(sqlBuilder.toString(), UserDTO.class);
        for (int i = 0; i < params.size(); i++) {
            dataQuery.setParameter(i + 1, params.get(i));
        }
        dataQuery.setFirstResult(request.getPage() * request.getSize());
        dataQuery.setMaxResults(request.getSize());
        List<UserDTO> content = dataQuery.getResultList();
        
        return new PageImpl<>(content, PageRequest.of(request.getPage(), request.getSize()), total);
    }
}

方案3:直接使用Criteria Builder(灵活度最高)

如果需要更精细的查询控制,可以直接用JPA Criteria Builder手动构建动态查询,底层和Specifications一致,但更灵活:

@Service
public class UserService {
    @Autowired
    private EntityManager em;

    public Page<User> getLazyUserDataWithCriteria(LazyLoadRequest request) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery<User> cq = cb.createQuery(User.class);
        Root<User> root = cq.from(User.class);
        
        // 构建过滤条件
        List<Predicate> predicates = new ArrayList<>();
        request.getFilters().forEach((field, value) -> {
            switch(field) {
                case "username":
                    predicates.add(cb.like(cb.lower(root.get("username")), "%" + value.toLowerCase() + "%"));
                    break;
                case "status":
                    predicates.add(cb.equal(root.get("status"), value));
                    break;
            }
        });
        cq.where(cb.and(predicates.toArray(new Predicate[0])));
        
        // 处理排序
        if (request.getSortField() != null) {
            Order order = request.getSortOrder() == -1 ? 
                          cb.desc(root.get(request.getSortField())) : 
                          cb.asc(root.get(request.getSortField()));
            cq.orderBy(order);
        }
        
        // 分页查询数据
        TypedQuery<User> dataQuery = em.createQuery(cq);
        dataQuery.setFirstResult(request.getPage() * request.getSize());
        dataQuery.setMaxResults(request.getSize());
        List<User> content = dataQuery.getResultList();
        
        // 统计总条数
        CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
        countQuery.select(cb.count(countQuery.from(User.class)));
        countQuery.where(cb.and(predicates.toArray(new Predicate[0])));
        long total = em.createQuery(countQuery).getSingleResult();
        
        return new PageImpl<>(content, PageRequest.of(request.getPage(), request.getSize()), total);
    }
}

核心注意事项

  1. 参数映射:PrimeNG的LazyLoadEvent中,first是起始索引,rows是每页数量,后端要转换为page = first / rows;sortOrder为1是升序,-1是降序。
  2. SQL注入防护:所有过滤参数必须通过占位符传递,绝对不能直接拼接字符串到SQL中。
  3. 性能优化:确保过滤字段、排序字段都有数据库索引,避免大数据量下的全表扫描。
  4. 复杂结果映射:如果原生查询返回多表字段,用DTO接收,可通过@SqlResultSetMapping或构造函数映射结果。

内容的提问来源于stack exchange,提问作者Karim Hossam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 02:24:13