zoukankan      html  css  js  c++  java
  • Spring MVC&Spring Data JPA过滤数据的另一种API

    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。

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

    分享这篇文章


     

    所有内容版权,这是我如何滚动 ©2014-2015•保留所有权利。自豪地与发表
  • 相关阅读:
    mysql常用命令
    【转】Hibernate级联注解CascadeType参数详解
    【转】el表达式的判断符
    js中使用进行字符串传参
    【转】HTML5 jQuery图片上传前预览
    Win10家庭版安装Docker Desktop报错:Containers Windows Feature is not available
    采用反射机制,得出属性是否忽略
    Windows 我的常用命令
    数据相关需要注意,工作所遇场景
    引入 QueryDsl 开发步骤
  • 原文地址:https://www.cnblogs.com/lize1215/p/7805105.html
Copyright © 2011-2022 走看看