Spring MVC&Spring Data JPA过滤数据的另一种API

这就是我滚动的方式2014年3月23日在春天mvc | 规格 | 春季资料 | jpa | 参数解析器 | java | 搜索 | 搜索 | 滤波

Spring MVC&Spring Data JPA过滤数据的另一种API

更新 自从我写这篇文章以来已经有一段时间了。我仍然认为它值得阅读,但请务必检查Github页面,因为所描述的库已经发展,并已成为Maven Central中的一个完整的开源项目。



我坚信,一个卓越的框架最终会成为一种(领域特定的)语言。

我已经使用了Spring MVC好几年了,但是我仍然对你定义控制器的灵活性印象深刻。它不再只是一堆实现的接口,而是一组构建块,使程序员能够流利地表达他们的Web应用程序。这更好,因为Spring给了我们一个API来扩展它。

Spring Data Web是使用此API来扩展Spring MVC控制器定义“语言”的一个很好的例子。例如,它提供了一个HandlerMethodArgumentResolver允许你使用一个Pageable对象作为你的控制器方法的参数。这很酷 - 不需要手动绑定HTTP参数来处理分页,这在几乎所有的企业系统中都是存在的。

实际上,在处理表格数据时,我们通常必须实现另一个功能(排序和分页除外)。这是数据过滤(或搜索)。通过自定义HandlerMethodArgumentResolver,可以以通用和声明的方式进行处理。我接受了实施的挑战,并希望与您分享结果。

通过单个属性进行过滤

最简单的例子是通过一个属性来查找实体。假设我们有Customer实体,我们希望找到提供名字的所有客户。网络请求可能如下所示:

GET http://myhost/api/customers?firstName=Homer

在我实现的微库中,相应的控制器方法可以具有如下形式:

@RequestMapping(value = "/customers", params = "firstName")
public Iterable<Customer> findByFirstName(
      @Spec(path = "firstName", spec = Like.class) Specification<Customer> spec) {

    return customerRepo.findAll(spec);
}

正如你所看到的,它是普通的Spring MVC和Spring Data加上一个自定义@Spec注释。注释指向Like实现谓词的类(在这种情况下)。谓词通过应用程序配置中指定的自定义参数解析器自动实例化。春数据储存库(customerRepo)使用所提供的标准来执行与以下where子句的查询:where firstName like ‘%Homer%‘

我们来看看这个例子中使用的组件:

  1. 参数是类型的Specification<Customer>。这是JPA标准API的更高层包装。Spring Data JPA提供的接口是 Eric Evan的领域驱动设计书中介绍的规范模式导出的。
  2. @Spec注释包含定义Web请求的解释的元数据。该路径属性指定过滤实体的财产。默认情况下,过滤模式预期为具有与属性路径(本例中为firstName)相同名称的Web参数的值。

    注释的spec属性指向要使用的Specification实现。在这种情况下Like,对于指定路径,它将与使用like %pattern%where子句提供的模式相匹配。

  3. 该方法Specification由Spring MVC和自定义自动提供HandlerMethodArgumentResolver。解析器本身处理注释并访问Web请求参数以实例化适当的Specification对象。

    解析器的实际实现可以在Github上的回购中找到。我没有在这里介绍代码,因为它实际上只是注释处理和一些反射。

规格

让我们仔细看看这个Specifications。Like课程实施如下:

public class Like<T> extends PathSpecification<T> {

    private String pattern;

    public Like(String path, String... args) {
        super(path);
        if (args == null || args.length != 1) {
            throw new IllegalArgumentException(
                "Expected exactly one argument (a fragment to match against)");
        } else {
            // (caution! some additional validation is required!)
            this.pattern = "%" + args[0] + "%";
        }
    }

    @Override
    public Predicate toPredicate(Root<T> root, CriteriaQuery<?> q, CriteriaBuilder cb) {
        return cb.like(this.<String>path(root), pattern);
    }
}

重要的部分是:

  • 你可能想知道为什么构造函数没有明确地使用一个模式参数,而是一个名为args的字符串数组。这是为了保持参数解析器独立Specification于未来添加的任何新的实现(Open / Closed原理)。解析器只是从注解的spec参数中获取一个类,并使用反射调用其构造函数。它没有意识到它实例化的类的任何其他细节,这允许你Specification自由地添加一个新的。
  • toPredicate方法使用从Web请求中获取的路径模式(基于注释元数据)。这是常规的JPA标准代码。该path方法在超类中实现,其目标是解析像"firstName""address.street"到JPA表达式的字符串。
  • 为了关注一般假设,我跳过了一些额外的验证。在许多情况下,你想检查模式(并强制至少3个字符),以避免不受限制的查询。

同样,我们可以实现任何其他的规范来处理不同谓词的查询。

当web参数不存在时

思考如果请求中不存在firstName参数,会发生什么情况是值得的。那么,@RequestMapping这个参数是明确指定的,所以它是映射的一部分 - 如果它不存在,控制器方法将不会被调用。但是另一种方法就是像这样对方法进行注释:

@RequestMapping("/customers") // no params required
...

那么我们可以假设这些请求:

 GET http://myhost/api/customers
 GET http://myhost/api/customers?firstName=Homer

应该返回所有分别以名字过滤的客户和客户。

这是一个优雅的解决方案。参数解析器在指定的web参数不存在的情况下返回null@Spec空指定由Spring Data作为空标准处理,因此根本不需要过滤。所以下面的方法处理这两种情况:

@RequestMapping("/customers") // no ‘params‘ argument
public Iterable<Customer> findByFirstName(
      @Spec(path = "firstName", spec = Like.class) Specification<Customer> spec) {

    return customerRepo.findAll(spec);
}

当参数名称不同于属性名称时

默认情况下,Web参数名称与属性的属性路径相同。这很方便,但有时候我们想要重写这个规则来使参数名称更加明确。例如,假设我们想要查找在某个日期之前注册的所有客户。网络请求可能如下所示:

GET http://myhost/api/customers?registeredBefore=2014-03-10

在这种情况下,说registerBefore而不是仅使用属性名称(registrationDate)更有意义。处理上面的映射看起来像:

@RequestMapping("/customers")
public Iterable<Customer> findByRegistrationDate(
      @Spec(params = "registeredBefore"
            path = "registrationDate", spec = DateBefore.class) Specification spec) {

    return customerRepo.findAll(spec);
}

正如你所看到的,它使用了一个额外的属性(params)来指定web参数的名字。

另一个有趣的事情是在片段中使用的规范 - DateBefore。这个实现与Like之前描述的非常类似,但是您可能会对日期时间格式感到疑惑。这是一个好点!DateBefore类使用一些默认的格式,可以使用注释的可选config属性来重写@Spec

@Spec(path="registrationDate", params="registeredBefore",
      spec=DateBefore.class, config="yyyy-MM-dd")
...

解析器可以将这样的附加信息传递给规范构造器。

使用多个参数来构建规范

到目前为止,我们已经看到每个规范只使用一个web参数的例子。它不一定是这样的。需要两个参数的规范的一个很好的例子是表达式之间的日期,即where date between :after and :before。它可以很容易地处理,因为params属性@Spec可以接受一串字符串。控制器定义将变成:

@RequestMapping("/customers")
public Iterable<Customer> findByRegistrationDate(
      @Spec(params = {"registeredBefore", "registeredAfter"},
            path = "registrationDate", spec = DateBetween.class) Specification spec) {

    return customerRepo.findAll(spec);
}

通过多个属性进行过滤

有时我们想同时过滤多个属性。例如这个请求:

GET http://myhost/api/customers?firstName=Homer&lastName=Simpson

应通过firstNamelastName缩小客户名单。为了处理这样的请求,控制器方法可以被声明如下:

@RequestMapping(value = "/customers/", params = { "firstName", "lastName" })
public Iterable<Customer> find(
    @And(
      @Spec(path = "firstName", spec = Like.class),
      @Spec(path = "lastName", spec = Like.class)) Specification spec) {

  return customerRepo.find(spec);
}

正如你所看到的,这个参数现在被注释了@And,这是一个围绕多个@Spec注释的包装。结果,解决的规范转化为where firstName like ‘%Homer%‘ and lastName like ‘%Simpson%‘。我们可以使用@Or类似的注解来定义一个析取。

匹配单个Web参数的多个路径

通常需要一个搜索引擎,将所提供的短语与实体的多个属性进行匹配。例如,当用户输入“8976”时,首先检查客户号码,然后是增值税号码。结果集应该包括所有的客户,其中任何一个属性都包含给定的模式。搜索引擎足够用户友好,用户不必明确选择搜索条件。相应的请求可能只有一种形式:

GET http://myhost/api/customers?query=8976

以上示例中描述的API足以定义处理查询的方法:

@RequestMapping(value = "/customers")
public Iterable<Customer> find(
   @Or({
     @Spec(params="query", path="customerNumber", spec=Like.class),
     @Spec(params="query", path="vatId", spec=Like.class)}) Specification spec){

  return customerRepo.find(spec);
}

而已。我希望在这一点上面的声明很容易理解。

更复杂的查询

我实现的解析器处理更复杂的定义 - 例如,您可以@Or在一个内部有多个注释@And。我没有在这里展示这样的例子,但是如果你愿意,你可以在Github上的回购中找到它们

或者,您可以Specification在单个控制器方法中使用多个参数(使用不同的注释),然后以编程方式合并它们。你也可以在Github上找到一个样本。

结论

在这篇文章中,我介绍了一个用于声明式解析SpecficationSpring MVC控制器处理程序方法参数的API 。提出的实现是HandlerMethodArgumentResolver使用注释元数据和Web请求参数的自定义。我成功地在我的一个项目中使用它。我认为在寻找方法不是很复杂但很多的情况下,这可能是一个节省时间的方法。

此外,我认为这是一个有益的练习设计和实施这样的API。Spring为我们提供了很多机制来扩展它。HandlerMethodArgumentResolver接口就是其中之一,我鼓励您将其视为一种减少控制器中重复代码量的方法。

来源

  1. 这个库的源代码可以在Github上找到:https//github.com/tkaczmarzyk/specification-arg-resolver阅读README.mdCHANGELOG.md文件是值得的,因为它们可能包含了本文写作后引入的新功能的描述。
  2. 您可以从Maven Central存储库下载二进制发行版:
    <dependency>
        <groupId>net.kaczmarzyk</groupId>
        <artifactId>specification-arg-resolver</artifactId>
        <version>0.6.0</version>
    </dependency>
    
  3. 或者,您可以从我的私有Maven仓库获取最新的快照:
    <repository>
        <id>kaczmarzyk.net</id>
        <url>http://repo.kaczmarzyk.net</url>
    </repository>
    

    依赖性定义是:

    <dependency>
        <groupId>net.kaczmarzyk</groupId>
        <artifactId>specification-arg-resolver</artifactId>
        <version>0.6.0</version>
    </dependency>
    
  4. 一个简单的Web应用程序使用库可以在这个回购:https//github.com/tkaczmarzyk/specification-arg-resolver-example

    它建立在Spring Boot的基础之上,所以你不需要任何服务器来运行它 - 只需将它作为独立的Java应用程序来运行,并开始监听http://localhost:8080。它公开了用于客户数据库过滤的REST API。

Tomasz Kaczmarzyk

软件开发人员。Java,Scala和开源爱好者。相信测试代码至少和生产代码一样重要。热切地研究新技术。

分享这篇文章


评论由Disqus提供

订阅!

所有内容版权,这是我如何滚动 ?2014-2015?保留所有权利。自豪地与发表

时间: 2024-10-11 12:21:48

Spring MVC&Spring Data JPA过滤数据的另一种API的相关文章

使用Spring Data JPA进行数据分页与排序

一.导读 如果一次性加载成千上万的列表数据,在网页上显示将十分的耗时,用户体验不好.所以处理较大数据查询结果展现的时候,分页查询是必不可少的.分页查询必然伴随着一定的排序规则,否则分页数据的状态很难控制,导致用户可能在不同的页看到同一条数据.那么,本文的主要内容就是给大家介绍一下,如何使用Spring Data JPA进行分页与排序. 二.实体定义 我们使用一个简单的实体定义:Article(文章) @Data @AllArgsConstructor @NoArgsConstructor @Bu

玩转spring mvc(四)---在spring MVC中整合JPA

关于在Spring MVC中整合JPA是在我的上一篇关于spring mvc基本配置基础上进行的,所以大家先参考一下我的上一篇文章:http://blog.csdn.net/u012116457/article/details/43528111 接下来是需要新添加的一些文件: jdbc.properties: jdbc.driverClassName=com.mysql.jdbc.Driver jdbc.url=jdbc\:mysql\://localhost\:3306/tmos?useUni

spring mvc+ajax 实现json格式数据传递

使用ajax传递JSON对象 下面示例为ajax发送json对象,返回json格式数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 $.ajax({ url: "api/user", type: "POST", timeout: txnTimeOut, async: true, dataType: "json", data: {username : "lucy"}

Spring MVC 前后台传递json格式数据 Content type &#39;application/x-www-form-urlencoded;charset=UTF-8&#39; not supported

报错如下: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported 解决方案: 引入如下包: 问题既解决. Spring MVC 前后台传递json格式数据 Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported

freemarker + spring mvc + spring + mybatis + mysql + maven项目搭建

今天说说搭建项目,使用freemarker + spring mvc + spring + mybatis + mysql + maven搭建web项目. 先假设您已经配置好eclipse的maven,创建好一个maven的web项目--Demo.我这里是jdk1.7,tomcat7. 修改pom.xml如下: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/

SSM 即所谓的 Spring MVC + Spring + MyBatis 整合开发。

SSM 即所谓的 Spring MVC + Spring + MyBatis 整合开发.是目前企业开发比较流行的架构.代替了之前的SSH(Struts + Spring + Hibernate) 计划的架构组合 Sping MVC + Spring + MyBatis(非Ajax版) Sping MVC + Spring + MyBatis(Ajax版) Sping MVC + Spring + MyBatis(Ajax版 + JavaConfig) Spring Boot + MyBatis

SSM(spring mvc+spring+Mybatis)框架整合

最近用Idea开发,idea是一款综合的相对较新的Java IDE.Idea支持很多整合功能,我觉得挺好用的.Idea可以校正xml,支持jsp的调试.最让我喜欢的是,写spring配置文件的时候,写的一些路径都可以自己找到.classpath自己有代码的提示,超级的智能. 环境配置 在整合框架之前,先配置一下JER运行环境,配置maven仓库. 1.File--ProjectStructure--Project--New- 选择jdk的安装环境 2.File--Settings--Maven-

spring mvc Spring Data Redis RedisTemplate [转]

一.概念简介: Redis: Redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写,详细的信息在Redis官网上面有,因为我自己通过google等各种渠道去学习Redis,走了不少弯路,所以总结一条我认为不错的学习路径给大家: 1.<The Little Redis Book> 是一本开源PDF,只有29页的英文文档,看完后对Redis的基本概念应该差不多熟悉了,剩下的可以去Redis官网熟悉相关的命令. 2.<Redis设计与实现> 如果想继续深入,推

Spring MVC使用@ResponseBody返回JSON数据406问题解决方案

其实前面一篇关于zTree返回JSON数据的文章已经有一种解决方案了,但是当我今天在新公司搭建新环境的时候,发现决然又不行了,所以我觉得那应该不是最优的解决方案. 说起来,我以前接触到的一个项目,根本没有配置spring的文件,就直接用@ResponseBody可以返回JSON数据,不知道其中的秘诀在什么地方,搞不懂了. 今天主要提供另一个解决@ResponseBody返回JSON数据,页面抛出406错误的解决方案. 第一步,引入包: <dependency> <groupId>c