JPQL虽然简单快捷,但面对复杂动态查询时可能不太方便,毕竟如果查询条件都是动态的,难道我们还要自己用复杂的代码逻辑拼接JPQL字符串吗?实际上,JPA中还支持Criteria查询。它和JPQL不同,Criteria查询是完全使用Java代码构造的,Criteria查询用于构建复杂的动态查询比JPQL更合适。
下面例子代码我们使用CriteriaAPI实现了一个动态查询,当queryStudentDto
中的查询条件存在时就将其拼接到动态查询下。
package com.gacfox.netstore.ejb;
import com.gacfox.netstore.api.DemoService;
import com.gacfox.netstore.api.model.QueryStudentDto;
import com.gacfox.netstore.ejb.entity.Student;
import org.jboss.logging.Logger;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.List;
@Stateless
public class DemoServiceImpl implements DemoService {
@PersistenceContext(unitName = "jpa-netstorer-pu")
private EntityManager entityManager;
private static final Logger logger = Logger.getLogger(HelloServiceImpl.class);
@Override
public void demo(QueryStudentDto queryStudentDto) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Student> query = criteriaBuilder.createQuery(Student.class);
Root<Student> root = query.from(Student.class);
// 构建动态查询条件
Predicate predicate = criteriaBuilder.conjunction();
if (queryStudentDto.getName() != null) {
predicate.getExpressions().add(criteriaBuilder.like(root.get("name"), "%" + queryStudentDto.getName() + "%"));
}
if (queryStudentDto.getAge() != null) {
predicate.getExpressions().add(criteriaBuilder.equal(root.get("age"), queryStudentDto.getAge()));
}
query.where(predicate);
// 执行查询
List<Student> studentList = entityManager.createQuery(query).getResultList();
logger.infov("Result: {0}" + studentList.size());
}
}
代码中,CriteriaBuilder
是JPA中用于构建动态查询的接口,CriteriaQuery
是用于构建查询的类,Root
是根实体,用于从数据库中选择数据。Predicate
是用来查询的条件对象,criteriaBuilder.conjunction()
方法返回一个AND连接的空条件(相当于where 1=1
),它其实可以理解为就是返回一个AND连接的基础,我们可以将它看作是查询的起始点,然后通过添加其他条件来扩展这个查询。动态构造查询后,query.where(predicate)
将所有条件组合在一起,作为查询的WHERE子句。
Criteria查询乍一看可能确实比较复杂,但实际上它和SQL/JPQL查询的构建逻辑是类似的,都是构造声明式的结构化查询,API中并没有什么复杂的算法。
Predicate的用法十分灵活,调用criteriaBuilder.and()
和criteriaBuilder.or()
我们可以组合出非常复杂的动态查询逻辑。
下面两种写法都是拼接若干AND条件,第一种写法可以理解为先给出一个where 1=1
条件,然后再其后拼接其它条件;第二种写法可以理解为将conditon1
和condition2
合并为AND条件,它们是等效的,区别主要在于代码的风格和表达方式。
Predicate finalCondition = criteriaBuilder.conjunction();
finalCondition.getExpressions().add(conditon1);
finalCondition.getExpressions().add(conditon2);
query.where(finalCondition);
Predicate finalCondition = criteriaBuilder.and(conditon1, conditon2);
query.where(finalCondition);
对于第二种写法,动态查询的条件可能是不定长的,criteriaBuilder.and()
方法的参数其实也是不定长的,实际开发中我们也可以传入一个数组类型的参数。
List<Predicate> predicates = new ArrayList<>();
predicates.add(conditon1);
predicates.add(conditon2);
Predicate finalCondition = criteriaBuilder.and(predicates.toArray(new Predicate[0]));
query.where(finalCondition);
但我们要注意它的参数不能是空数组,如果用户一个条件都没有传入,传入空数组会报错,因此我们可能还是需要传入一个criteriaBuilder.conjunction()
条件作为起始条件查询全部数据,或者直接返回空拒绝这种不带条件的查询。
另外对于OR条件,我们可以使用criteriaBuilder.or()
方法生成,它的参数也是不定长的,可以传入多个条件或传入数组。与AND查询类似,如果用户没有查询条件,我们可能还需要拼接一个criteriaBuilder.disjunction()
空条件。
下面例子组合拼接了AND和OR查询条件,嵌套拼接这些Predicate
便可以组合出复杂的查询逻辑。
Predicate andCondition = criteriaBuilder.and(conditon1, conditon2);
Predicate orCondition = criteriaBuilder.or(conditon3, conditon4);
Predicate finalCondition = criteriaBuilder.and(andCondition, orCondition);
query.where(finalCondition);