diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/task/TaskService.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/task/TaskService.java index 22be81dc..d3c2cfbd 100644 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/task/TaskService.java +++ b/examples/quickstart/src/main/java/org/springside/examples/quickstart/service/task/TaskService.java @@ -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) taskDao.findAll(); } - public Page getUserTask(Long userId, int pageNumber, int pageSize, String sortType) { + public Page getUserTask(Long userId, Map filterParams, int pageNumber, int pageSize, + String sortType) { PageRequest pageRequest = buildPageRequest(pageNumber, pageSize, sortType); - return taskDao.findByUserId(userId, pageRequest); + Specification 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 buildSpecification(Long userId, Map filterParams) { + List filters = SearchFilter.parse(filterParams); + filters.add(new SearchFilter("user.id", Operator.EQ, userId)); + Specification spec = specBuilder.byWebFilter(filters, Task.class); + return spec; + } + @Autowired public void setTaskDao(TaskDao taskDao) { this.taskDao = taskDao; diff --git a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/task/TaskController.java b/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/task/TaskController.java index d073e6ff..829ae013 100644 --- a/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/task/TaskController.java +++ b/examples/quickstart/src/main/java/org/springside/examples/quickstart/web/task/TaskController.java @@ -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 searchParams = Servlets.getParametersStartingWith(request, "search"); + Long userId = getCurrentUserId(); - Page tasks = taskService.getUserTask(userId, pageNumber, PAGE_SIZE, sortType); + + Page tasks = taskService.getUserTask(userId, searchParams, pageNumber, PAGE_SIZE, sortType); model.addAttribute("tasks", tasks); model.addAttribute("sortType", sortType); diff --git a/examples/quickstart/src/test/java/org/springside/examples/quickstart/repository/TaskDaoTest.java b/examples/quickstart/src/test/java/org/springside/examples/quickstart/repository/TaskDaoTest.java index 9f9a7c12..1c8d64fd 100644 --- a/examples/quickstart/src/test/java/org/springside/examples/quickstart/repository/TaskDaoTest.java +++ b/examples/quickstart/src/test/java/org/springside/examples/quickstart/repository/TaskDaoTest.java @@ -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 tasks = taskDao.findAll(spec.byWebFilter(Lists.newArrayList(filter), Task.class)); + List 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()); } } diff --git a/modules/core/src/main/java/org/springside/modules/persistence/ByWebFilterSpecification.java b/modules/core/src/main/java/org/springside/modules/persistence/ByWebFilterSpecification.java index 6e75d2dd..b16eec8c 100644 --- a/modules/core/src/main/java/org/springside/modules/persistence/ByWebFilterSpecification.java +++ b/modules/core/src/main/java/org/springside/modules/persistence/ByWebFilterSpecification.java @@ -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 Specification byWebFilter(final List filters, final Class clazz) { + public Specification byWebFilter(final Collection filters, final Class clazz) { return new Specification() { @Override public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder) { - List predicates = Lists.newArrayList(); + if (Collections3.isNotEmpty(filters)) { - for (SearchFilter filter : filters) { - SingularAttribute attr = entityManager.getMetamodel().entity(clazz) - .getSingularAttribute(filter.fieldName); + List 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) root.get(attr), "%" + filter.value + "%")); - break; - case GT: - predicates.add(builder.greaterThan((Expression) root.get(attr), - (Comparable) filter.value)); - break; - case LT: - predicates.add(builder.lessThan((Expression) root.get(attr), - (Comparable) filter.value)); - break; - case GTE: - predicates.add(builder.greaterThanOrEqualTo((Expression) root.get(attr), - (Comparable) filter.value)); - break; - case LTE: - predicates.add(builder.lessThanOrEqualTo((Expression) 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(); } }; diff --git a/modules/core/src/main/java/org/springside/modules/persistence/SearchFilter.java b/modules/core/src/main/java/org/springside/modules/persistence/SearchFilter.java index 6e3439a1..e2a56438 100644 --- a/modules/core/src/main/java/org/springside/modules/persistence/SearchFilter.java +++ b/modules/core/src/main/java/org/springside/modules/persistence/SearchFilter.java @@ -24,14 +24,14 @@ public class SearchFilter { this.operator = operator; } - public static List parse(Map filterParams) { + public static List parse(Map filterParams) { List filters = Lists.newArrayList(); - for (Entry entry : filterParams.entrySet()) { + + for (Entry 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); } diff --git a/modules/core/src/main/java/org/springside/modules/utils/Collections3.java b/modules/core/src/main/java/org/springside/modules/utils/Collections3.java index dbe8ace2..5ba7b6f0 100644 --- a/modules/core/src/main/java/org/springside/modules/utils/Collections3.java +++ b/modules/core/src/main/java/org/springside/modules/utils/Collections3.java @@ -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 list = (List) collection; return list.get(list.size() - 1); } - //其他类型通过iterator滚动到最后一个元素. + // 其他类型通过iterator滚动到最后一个元素. Iterator iterator = collection.iterator(); while (true) { T current = iterator.next(); diff --git a/modules/core/src/test/java/org/springside/modules/persistence/SearchFilterTest.java b/modules/core/src/test/java/org/springside/modules/persistence/SearchFilterTest.java index 0fef511d..92dc8c68 100644 --- a/modules/core/src/test/java/org/springside/modules/persistence/SearchFilterTest.java +++ b/modules/core/src/test/java/org/springside/modules/persistence/SearchFilterTest.java @@ -15,7 +15,7 @@ public class SearchFilterTest { @Test public void normal() { // linkedHashMap保证顺序 - Map params = Maps.newLinkedHashMap(); + Map 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 params = Maps.newLinkedHashMap(); + Map params = Maps.newLinkedHashMap(); params.put("EQ", "foo"); SearchFilter.parse(params); @@ -45,7 +45,7 @@ public class SearchFilterTest { } try { - Map params = Maps.newLinkedHashMap(); + Map params = Maps.newLinkedHashMap(); params.put("EQ_", "foo"); SearchFilter.parse(params); @@ -54,7 +54,7 @@ public class SearchFilterTest { } try { - Map params = Maps.newLinkedHashMap(); + Map params = Maps.newLinkedHashMap(); params.put("ABC_name", "foo"); SearchFilter.parse(params);