#86 开始实现动态搜索条件组合的功能, 支持嵌入式查询,另精简API使用。
This commit is contained in:
+24
-3
@@ -1,25 +1,33 @@
|
||||
package org.springside.examples.quickstart.service.task;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springside.examples.quickstart.entity.Task;
|
||||
import org.springside.examples.quickstart.repository.TaskDao;
|
||||
import org.springside.modules.persistence.ByWebFilterSpecification;
|
||||
import org.springside.modules.persistence.SearchFilter;
|
||||
import org.springside.modules.persistence.SearchFilter.Operator;
|
||||
|
||||
//Spring Bean的标识.
|
||||
@Component
|
||||
//默认将类中的所有public函数纳入事务管理.
|
||||
// 默认将类中的所有public函数纳入事务管理.
|
||||
@Transactional(readOnly = true)
|
||||
public class TaskService {
|
||||
|
||||
private TaskDao taskDao;
|
||||
|
||||
@Autowired
|
||||
private ByWebFilterSpecification specBuilder;
|
||||
|
||||
public Task getTask(Long id) {
|
||||
return taskDao.findOne(id);
|
||||
}
|
||||
@@ -38,9 +46,12 @@ public class TaskService {
|
||||
return (List<Task>) taskDao.findAll();
|
||||
}
|
||||
|
||||
public Page<Task> getUserTask(Long userId, int pageNumber, int pageSize, String sortType) {
|
||||
public Page<Task> getUserTask(Long userId, Map<String, Object> filterParams, int pageNumber, int pageSize,
|
||||
String sortType) {
|
||||
PageRequest pageRequest = buildPageRequest(pageNumber, pageSize, sortType);
|
||||
return taskDao.findByUserId(userId, pageRequest);
|
||||
Specification<Task> spec = buildSpecification(userId, filterParams);
|
||||
|
||||
return taskDao.findAll(spec, pageRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,6 +68,16 @@ public class TaskService {
|
||||
return new PageRequest(pageNumber - 1, pagzSize, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动态查询条件组合.
|
||||
*/
|
||||
private Specification<Task> buildSpecification(Long userId, Map<String, Object> filterParams) {
|
||||
List<SearchFilter> filters = SearchFilter.parse(filterParams);
|
||||
filters.add(new SearchFilter("user.id", Operator.EQ, userId));
|
||||
Specification<Task> spec = specBuilder.byWebFilter(filters, Task.class);
|
||||
return spec;
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setTaskDao(TaskDao taskDao) {
|
||||
this.taskDao = taskDao;
|
||||
|
||||
+14
-8
@@ -2,6 +2,7 @@ package org.springside.examples.quickstart.web.task;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.validation.Valid;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
@@ -19,18 +20,19 @@ import org.springside.examples.quickstart.entity.Task;
|
||||
import org.springside.examples.quickstart.entity.User;
|
||||
import org.springside.examples.quickstart.service.account.ShiroDbRealm.ShiroUser;
|
||||
import org.springside.examples.quickstart.service.task.TaskService;
|
||||
import org.springside.modules.web.Servlets;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
/**
|
||||
* Task管理的Controller, 使用Restful风格的Urls:
|
||||
*
|
||||
* List page : GET /task/
|
||||
* Create page : GET /task/create
|
||||
* Create action : POST /task/create
|
||||
* Update page : GET /task/update/{id}
|
||||
* Update action : POST /task/update
|
||||
* Delete action : GET /task/delete/{id}
|
||||
* List page : GET /task/
|
||||
* Create page : GET /task/create
|
||||
* Create action : POST /task/create
|
||||
* Update page : GET /task/update/{id}
|
||||
* Update action : POST /task/update
|
||||
* Delete action : GET /task/delete/{id}
|
||||
*
|
||||
* @author calvin
|
||||
*/
|
||||
@@ -51,9 +53,13 @@ public class TaskController {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
public String list(@RequestParam(value = "sortType", defaultValue = "auto") String sortType,
|
||||
@RequestParam(value = "page", defaultValue = "1") int pageNumber, Model model) {
|
||||
@RequestParam(value = "page", defaultValue = "1") int pageNumber, Model model, ServletRequest request) {
|
||||
|
||||
Map<String, Object> searchParams = Servlets.getParametersStartingWith(request, "search");
|
||||
|
||||
Long userId = getCurrentUserId();
|
||||
Page<Task> tasks = taskService.getUserTask(userId, pageNumber, PAGE_SIZE, sortType);
|
||||
|
||||
Page<Task> tasks = taskService.getUserTask(userId, searchParams, pageNumber, PAGE_SIZE, sortType);
|
||||
model.addAttribute("tasks", tasks);
|
||||
|
||||
model.addAttribute("sortType", sortType);
|
||||
|
||||
+27
-13
@@ -2,6 +2,7 @@ package org.springside.examples.quickstart.repository;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
@@ -25,7 +26,7 @@ public class TaskDaoTest extends SpringTransactionalTestCase {
|
||||
private TaskDao taskDao;
|
||||
|
||||
@Autowired
|
||||
private ByWebFilterSpecification spec;
|
||||
private ByWebFilterSpecification specBuilder;
|
||||
|
||||
@Test
|
||||
public void findTasksByUserId() throws Exception {
|
||||
@@ -39,57 +40,70 @@ public class TaskDaoTest extends SpringTransactionalTestCase {
|
||||
|
||||
@Test
|
||||
public void fineTasksByFilter() {
|
||||
|
||||
// EQ
|
||||
SearchFilter filter = new SearchFilter("title", Operator.EQ, "Study PlayFramework 2.0");
|
||||
List<Task> tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
List<Task> tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(1, tasks.size());
|
||||
// LIKE
|
||||
filter = new SearchFilter("description", Operator.LIKE, "playframework");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(1, tasks.size());
|
||||
// GT
|
||||
filter = new SearchFilter("id", Operator.GT, "1");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(4, tasks.size());
|
||||
|
||||
filter = new SearchFilter("id", Operator.GT, "5");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(0, tasks.size());
|
||||
// GTE
|
||||
filter = new SearchFilter("id", Operator.GTE, "1");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(5, tasks.size());
|
||||
|
||||
filter = new SearchFilter("id", Operator.GTE, "5");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(1, tasks.size());
|
||||
|
||||
// LT
|
||||
filter = new SearchFilter("id", Operator.LT, "5");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(4, tasks.size());
|
||||
|
||||
filter = new SearchFilter("id", Operator.LT, "1");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(0, tasks.size());
|
||||
// LTE
|
||||
filter = new SearchFilter("id", Operator.LTE, "5");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(5, tasks.size());
|
||||
|
||||
filter = new SearchFilter("id", Operator.LTE, "1");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(1, tasks.size());
|
||||
|
||||
// Empty filters
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(new ArrayList(), Task.class));
|
||||
assertEquals(5, tasks.size());
|
||||
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(null, Task.class));
|
||||
assertEquals(5, tasks.size());
|
||||
|
||||
// AND 2 Conditions
|
||||
SearchFilter filter1 = new SearchFilter("title", Operator.EQ, "Study PlayFramework 2.0");
|
||||
SearchFilter filter2 = new SearchFilter("description", Operator.LIKE, "playframework");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter1, filter2), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter1, filter2), Task.class));
|
||||
assertEquals(1, tasks.size());
|
||||
|
||||
filter1 = new SearchFilter("title", Operator.EQ, "Study PlayFramework 2.0");
|
||||
filter2 = new SearchFilter("description", Operator.LIKE, "springfuse");
|
||||
tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter1, filter2), Task.class));
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter1, filter2), Task.class));
|
||||
assertEquals(0, tasks.size());
|
||||
|
||||
// Nest Attribute
|
||||
filter = new SearchFilter("user.id", Operator.EQ, "2");
|
||||
tasks = taskDao.findAll(specBuilder.byWebFilter(Lists.newArrayList(filter), Task.class));
|
||||
assertEquals(5, tasks.size());
|
||||
}
|
||||
}
|
||||
|
||||
+44
-44
@@ -1,75 +1,75 @@
|
||||
package org.springside.modules.persistence;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Expression;
|
||||
import javax.persistence.criteria.Path;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import javax.persistence.metamodel.SingularAttribute;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springside.modules.utils.Collections3;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
public class ByWebFilterSpecification {
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
private final ConversionService conversionService = new DefaultConversionService();
|
||||
|
||||
public <T> Specification<T> byWebFilter(final List<SearchFilter> filters, final Class<T> clazz) {
|
||||
public <T> Specification<T> byWebFilter(final Collection<SearchFilter> filters, final Class<T> clazz) {
|
||||
return new Specification<T>() {
|
||||
@Override
|
||||
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||||
List<Predicate> predicates = Lists.newArrayList();
|
||||
if (Collections3.isNotEmpty(filters)) {
|
||||
|
||||
for (SearchFilter filter : filters) {
|
||||
SingularAttribute<? super T, ?> attr = entityManager.getMetamodel().entity(clazz)
|
||||
.getSingularAttribute(filter.fieldName);
|
||||
List<Predicate> predicates = Lists.newArrayList();
|
||||
for (SearchFilter filter : filters) {
|
||||
|
||||
// convert attribute
|
||||
Class attributeClass = attr.getJavaType();
|
||||
if (!attributeClass.equals(String.class) && filter.value instanceof String
|
||||
&& conversionService.canConvert(String.class, attributeClass)) {
|
||||
filter.value = conversionService.convert(filter.value, attributeClass);
|
||||
// nested path translate
|
||||
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]);
|
||||
}
|
||||
|
||||
// convert value
|
||||
Class attributeClass = expression.getJavaType();
|
||||
if (!attributeClass.equals(String.class) && filter.value instanceof String
|
||||
&& conversionService.canConvert(String.class, attributeClass)) {
|
||||
filter.value = conversionService.convert(filter.value, attributeClass);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
switch (filter.operator) {
|
||||
case EQ:
|
||||
predicates.add(builder.equal(root.get(attr), filter.value));
|
||||
break;
|
||||
case LIKE:
|
||||
predicates.add(builder.like((Expression<String>) root.get(attr), "%" + filter.value + "%"));
|
||||
break;
|
||||
case GT:
|
||||
predicates.add(builder.greaterThan((Expression<Comparable>) root.get(attr),
|
||||
(Comparable) filter.value));
|
||||
break;
|
||||
case LT:
|
||||
predicates.add(builder.lessThan((Expression<Comparable>) root.get(attr),
|
||||
(Comparable) filter.value));
|
||||
break;
|
||||
case GTE:
|
||||
predicates.add(builder.greaterThanOrEqualTo((Expression<Comparable>) root.get(attr),
|
||||
(Comparable) filter.value));
|
||||
break;
|
||||
case LTE:
|
||||
predicates.add(builder.lessThanOrEqualTo((Expression<Comparable>) root.get(attr),
|
||||
(Comparable) filter.value));
|
||||
break;
|
||||
if (predicates.size() > 0) {
|
||||
return builder.and(predicates.toArray(new Predicate[predicates.size()]));
|
||||
}
|
||||
}
|
||||
|
||||
if (predicates.size() > 0) {
|
||||
return builder.and(predicates.toArray(new Predicate[predicates.size()]));
|
||||
}
|
||||
|
||||
return builder.conjunction();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -24,14 +24,14 @@ public class SearchFilter {
|
||||
this.operator = operator;
|
||||
}
|
||||
|
||||
public static List<SearchFilter> parse(Map<String, String> filterParams) {
|
||||
public static List<SearchFilter> parse(Map<String, Object> filterParams) {
|
||||
List<SearchFilter> filters = Lists.newArrayList();
|
||||
for (Entry<String, String> entry : filterParams.entrySet()) {
|
||||
|
||||
for (Entry<String, Object> entry : filterParams.entrySet()) {
|
||||
String[] names = StringUtils.split(entry.getKey(), "_");
|
||||
if (names.length != 2) {
|
||||
throw new IllegalArgumentException(entry.getKey() + " is not a valid search filter name");
|
||||
}
|
||||
|
||||
SearchFilter filter = new SearchFilter(names[1], Operator.valueOf(names[0]), entry.getValue());
|
||||
filters.add(filter);
|
||||
}
|
||||
|
||||
@@ -106,6 +106,13 @@ public class Collections3 {
|
||||
return (collection == null || collection.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为空.
|
||||
*/
|
||||
public static boolean isNotEmpty(Collection collection) {
|
||||
return (collection != null && !(collection.isEmpty()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得Collection的第一个元素,如果collection为空返回null.
|
||||
*/
|
||||
@@ -125,13 +132,13 @@ public class Collections3 {
|
||||
return null;
|
||||
}
|
||||
|
||||
//当类型为List时,直接取得最后一个元素 。
|
||||
// 当类型为List时,直接取得最后一个元素 。
|
||||
if (collection instanceof List) {
|
||||
List<T> list = (List<T>) collection;
|
||||
return list.get(list.size() - 1);
|
||||
}
|
||||
|
||||
//其他类型通过iterator滚动到最后一个元素.
|
||||
// 其他类型通过iterator滚动到最后一个元素.
|
||||
Iterator<T> iterator = collection.iterator();
|
||||
while (true) {
|
||||
T current = iterator.next();
|
||||
|
||||
@@ -15,7 +15,7 @@ public class SearchFilterTest {
|
||||
@Test
|
||||
public void normal() {
|
||||
// linkedHashMap保证顺序
|
||||
Map<String, String> params = Maps.newLinkedHashMap();
|
||||
Map<String, Object> params = Maps.newLinkedHashMap();
|
||||
params.put("EQ_name", "foo");
|
||||
params.put("LT_age", "1");
|
||||
|
||||
@@ -36,7 +36,7 @@ public class SearchFilterTest {
|
||||
public void wrongName() {
|
||||
|
||||
try {
|
||||
Map<String, String> params = Maps.newLinkedHashMap();
|
||||
Map<String, Object> params = Maps.newLinkedHashMap();
|
||||
params.put("EQ", "foo");
|
||||
|
||||
SearchFilter.parse(params);
|
||||
@@ -45,7 +45,7 @@ public class SearchFilterTest {
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, String> params = Maps.newLinkedHashMap();
|
||||
Map<String, Object> params = Maps.newLinkedHashMap();
|
||||
params.put("EQ_", "foo");
|
||||
|
||||
SearchFilter.parse(params);
|
||||
@@ -54,7 +54,7 @@ public class SearchFilterTest {
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, String> params = Maps.newLinkedHashMap();
|
||||
Map<String, Object> params = Maps.newLinkedHashMap();
|
||||
params.put("ABC_name", "foo");
|
||||
|
||||
SearchFilter.parse(params);
|
||||
|
||||
Reference in New Issue
Block a user