#86 开始实现动态搜索条件组合的功能, 支持嵌入式查询,另精简API使用。

This commit is contained in:
Calvin
2012-09-19 17:41:49 +08:00
parent b654997450
commit 49c084cb3d
7 changed files with 125 additions and 77 deletions
@@ -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;
@@ -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);
@@ -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());
}
}
@@ -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);