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更简洁易读,适合复杂动态查询场景。
步骤
- 引入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>
- 配置APT插件生成实体对应的Q类(编译阶段自动生成)
- 编写通用查询方法
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
相关产品推荐
相关产品推荐

