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

Spring Boot中基于Hibernate/JPA实现通用多条件查询方法

用Hibernate/JPA实现通用动态查询(适配多属性搜索表单)

一、JPA Criteria API 核心实现

这是JPA标准提供的动态查询方案,专门解决多属性搜索场景下无需编写大量重复查询方法的问题。

核心思路

封装通用查询方法,接收实体类类型和搜索条件DTO,通过CriteriaBuilder动态生成查询条件(Predicate),最终拼接成可执行的CriteriaQuery。

代码示例

假设你有User实体和对应搜索DTOUserSearchDTO(包含所有表单输入项),可以实现如下通用查询服务:

import jakarta.persistence.EntityManager;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import org.springframework.stereotype.Service;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

@Service
public class GenericQueryService {

    private final EntityManager entityManager;

    public GenericQueryService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public <T, DTO> List<T> search(Class<T> entityClass, DTO searchDto) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> cq = cb.createQuery(entityClass);
        Root<T> root = cq.from(entityClass);

        List<Predicate> predicates = new ArrayList<>();
        Field[] dtoFields = searchDto.getClass().getDeclaredFields();

        for (Field field : dtoFields) {
            field.setAccessible(true);
            try {
                Object value = field.get(searchDto);
                if (value != null) {
                    String entityFieldName = field.getName(); // 假设DTO与实体字段名一致
                    if (value instanceof String strValue) {
                        predicates.add(cb.like(root.get(entityFieldName), "%" + strValue + "%"));
                    } else if (value instanceof Number numValue) {
                        predicates.add(cb.equal(root.get(entityFieldName), numValue));
                    } else if (value instanceof Boolean boolValue) {
                        predicates.add(cb.equal(root.get(entityFieldName), boolValue));
                    }
                    // 可扩展日期、枚举等类型的匹配逻辑
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

        cq.where(cb.and(predicates.toArray(new Predicate[0])));
        return entityManager.createQuery(cq).getResultList();
    }
}

使用方式

业务代码中直接调用即可:

UserSearchDTO dto = new UserSearchDTO();
dto.setUsername("john");
dto.setAge(25);
List<User> users = genericQueryService.search(User.class, dto);

扩展点

  • 若DTO与实体字段名不一致,可通过自定义注解(如@EntityField)做映射
  • 支持关联实体查询:例如root.join("role").get("roleName"),可在DTO中添加关联属性并单独处理
  • 增加分页、排序:通过cq.orderBy()和setFirstResult()/setMaxResults()实现

二、QueryDSL 简化动态查询

QueryDSL是第三方工具,通过静态类型检查构建查询,比JPA Criteria API更简洁易读,适合复杂动态查询场景。

步骤

  1. 引入Maven依赖
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-jpa</artifactId>
    <version>5.0.0</version>
</dependency>
<dependency>
    <groupId>com.querydsl</groupId>
    <artifactId>querydsl-apt</artifactId>
    <version>5.0.0</version>
    <scope>provided</scope>
</dependency>
  1. 配置APT插件生成实体对应的Q类(编译阶段自动生成)
  2. 编写通用查询方法
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;

@Service
public class QueryDslGenericService {

    private final JPAQueryFactory queryFactory;

    public QueryDslGenericService(JPAQueryFactory queryFactory) {
        this.queryFactory = queryFactory;
    }

    public List<User> searchUser(UserSearchDTO dto) {
        QUser qUser = QUser.user;
        List<com.querydsl.core.types.Predicate> predicates = new ArrayList<>();

        if (dto.getUsername() != null) {
            predicates.add(qUser.username.containsIgnoreCase(dto.getUsername()));
        }
        if (dto.getAge() != null) {
            predicates.add(qUser.age.eq(dto.getAge()));
        }
        // 其他属性同理扩展

        return queryFactory.selectFrom(qUser)
                .where(predicates.toArray(new com.querydsl.core.types.Predicate[0]))
                .fetch();
    }
}

这种方式完全避免反射,类型安全,代码可读性更高,适合大型项目使用。

三、Hibernate 原生 Criteria API(旧版)

若使用Hibernate 5之前的版本,可使用原生Criteria API,不过官方现在更推荐JPA标准方案或QueryDSL,核心用法如下:

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import java.util.List;

public List<User> searchWithHibernateCriteria(UserSearchDTO dto) {
    Session session = entityManager.unwrap(Session.class);
    Criteria criteria = session.createCriteria(User.class);

    if (dto.getUsername() != null) {
        criteria.add(Restrictions.like("username", "%" + dto.getUsername() + "%"));
    }
    if (dto.getAge() != null) {
        criteria.add(Restrictions.eq("age", dto.getAge()));
    }

    return criteria.list();
}

注意事项

  • 性能优化:反射实现的通用查询可缓存字段信息,减少重复反射开销;QueryDSL为静态生成,性能更优
  • 安全性:JPA Criteria和QueryDSL都会自动处理参数绑定,避免SQL注入风险
  • 复杂场景:多表关联、分组聚合等复杂查询,建议结合QueryDSL或手动构建CriteriaQuery,避免通用工具类过度臃肿

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 17:25:18