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

it2022-05-07  42

这就是我滚动的方式

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%'。

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

参数是类型的Specification<Customer>。这是JPA标准API的更高层包装。Spring Data JPA提供的接口是由 Eric Evan的领域驱动设计书中介绍的规范模式导出的。

该@Spec注释包含定义Web请求的解释的元数据。该路径属性指定过滤实体的财产。默认情况下,过滤模式预期为具有与属性路径(本例中为firstName)相同名称的Web参数的值。

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

该方法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

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

@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接口就是其中之一,我鼓励您将其视为一种减少控制器中重复代码量的方法。

来源

这个库的源代码可以在Github上找到:https://github.com/tkaczmarzyk/specification-arg-resolver。阅读README.md和CHANGELOG.md文件是值得的,因为它们可能包含了本文写作后引入的新功能的描述。

您可以从Maven Central存储库下载二进制发行版: <dependency> <groupId>net.kaczmarzyk</groupId> <artifactId>specification-arg-resolver</artifactId> <version>0.6.0</version> </dependency> 或者,您可以从我的私有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>

一个简单的Web应用程序使用库可以在这个回购:https://github.com/tkaczmarzyk/specification-arg-resolver-example

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

Tomasz Kaczmarzyk

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

分享这篇文章


 

订阅!

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

转载于:https://www.cnblogs.com/lize1215/p/7805105.html

相关资源:java服务端后台常用模板(Spring Data JPA、Hibernate、 Spring MVC、Layer)

最新回复(0)