本文主要研究下jpa的动态查询java
jpa从hibernate里头吸取了criteria,利用criteria结合对url查询语法的解析,也能够实现端到端的动态查询。
下面展现下springside branch 4版本中的实现。git
/******************************************************************************* * Copyright (c) 2005, 2014 springside.github.io * * Licensed under the Apache License, Version 2.0 (the "License"); *******************************************************************************/ package org.springside.modules.persistence; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.lang3.StringUtils; import com.google.common.collect.Maps; public class SearchFilter { public enum Operator { EQ, LIKE, GT, LT, GTE, LTE } public String fieldName; public Object value; public Operator operator; public SearchFilter(String fieldName, Operator operator, Object value) { this.fieldName = fieldName; this.value = value; this.operator = operator; } /** * searchParams中key的格式为OPERATOR_FIELDNAME */ public static Map<String, SearchFilter> parse(Map<String, Object> searchParams) { Map<String, SearchFilter> filters = Maps.newHashMap(); for (Entry<String, Object> entry : searchParams.entrySet()) { // 过滤掉空值 String key = entry.getKey(); Object value = entry.getValue(); if (StringUtils.isBlank((String) value)) { continue; } // 拆分operator与filedAttribute String[] names = StringUtils.split(key, "_"); if (names.length != 2) { throw new IllegalArgumentException(key + " is not a valid search filter name"); } String filedName = names[1]; Operator operator = Operator.valueOf(names[0]); // 建立searchFilter SearchFilter filter = new SearchFilter(filedName, operator, value); filters.put(key, filter); } return filters; } }
这里定义了几种运算符EQ, LIKE, GT, LT, GTE, LTE
能够本身从mvc的controller中接收查询条件转换为SearchFilter
/******************************************************************************* * Copyright (c) 2005, 2014 springside.github.io * * Licensed under the Apache License, Version 2.0 (the "License"); *******************************************************************************/ package org.springside.modules.persistence; import java.util.Collection; import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Path; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import org.apache.commons.lang3.StringUtils; import org.springframework.data.jpa.domain.Specification; import org.springside.modules.utils.Collections3; import com.google.common.collect.Lists; public class DynamicSpecifications { public static <T> Specification<T> bySearchFilter(final Collection<SearchFilter> filters, final Class<T> entityClazz) { return new Specification<T>() { @Override public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) { if (Collections3.isNotEmpty(filters)) { List<Predicate> predicates = Lists.newArrayList(); for (SearchFilter filter : filters) { // nested path translate, 如Task的名为"user.name"的filedName, 转换为Task.user.name属性 String[] names = StringUtils.split(filter.fieldName, "."); Path expression = root.get(names[0]); for (int i = 1; i < names.length; i++) { expression = expression.get(names[i]); } // logic operator switch (filter.operator) { case EQ: predicates.add(builder.equal(expression, filter.value)); break; case LIKE: predicates.add(builder.like(expression, "%" + filter.value + "%")); break; case GT: predicates.add(builder.greaterThan(expression, (Comparable) filter.value)); break; case LT: predicates.add(builder.lessThan(expression, (Comparable) filter.value)); break; case GTE: predicates.add(builder.greaterThanOrEqualTo(expression, (Comparable) filter.value)); break; case LTE: predicates.add(builder.lessThanOrEqualTo(expression, (Comparable) filter.value)); break; } } // 将全部条件用 and 联合起来 if (!predicates.isEmpty()) { return builder.and(predicates.toArray(new Predicate[predicates.size()])); } } return builder.conjunction(); } }; } }
这里主要是将SearchFilter构造的查询条件转换为对应的Predicate,进而构形成jpa的Specification,来完成动态查询条件的转换。
使用springside的DynamicSpecifications,再把mvc的参数映射为SearchFilter,也能够本身实现一套端到端的动态查询。github
备注springside在最新版本已经删除掉这些代码,得在branch 4分支查找。