3.3.4 自定义 Repository

想象一下,除了 CrudRepository 提供的基本 CRUD 操作之外,还需要获取投递给指定邮政编码的所有订单。事实证明,通过在 OrderRepository 中添加以下方法声明可以很容易地解决这个问题:

List<TacoOrder> findByDeliveryZip(String deliveryZip);

在生成 repository 实现时,Spring Data 检查存储库接口中的任何方法,解析方法名称,并尝试在持久化对象的上下文中理解方法的用途(在本例中是 TacoOrder)。本质上,Spring Data 定义了一种小型的领域特定语言(DSL),其中持久化细节用 repository 中的方法签名表示。

Spring Data 知道这个方法是用来查找订单的,因为已经用 TacoOrder 参数化了 CrudRepository。方法名 findByDeliveryZip() 表明,该方法应该通过将其 deliveryZip 属性与作为参数,传递给匹配的方法来查找所有订单实体。

findByDeliveryZip() 方法非常简单,但是 Spring Data 也可以处理更有趣的方法名。repository 的方法由一个动词、一个可选的主语、单词 by 和一个谓词组成。在 findByDeliveryZip() 中,动词是 find,谓词是 DeliveryZip,主语没有指定,暗示是一个 TacoOrder。

让我们考虑另一个更复杂的例子。假设需要查询在给定日期范围内投递给指定邮政编码的所有订单。在这种情况下,当添加到 OrderRepository 时,下面的方法可能会被证明是有用的:

List<TacoOrder> readOrdersByDeliveryZipAndPlacedAtBetween(
      String deliveryZip, Date startDate, Date endDate);

图 3.2 说明了在生成 respository 实现时,Spring Data 如何解析和理解 readOrdersByDeliveryZipAndPlacedAtBetween() 方法。可以看到,readOrdersByDeliveryZipAndPlacedAtBetween() 中的动词是 read。Spring Data 还将 find、read 和 get 理解为获取一个或多个实体的同义词。另外,如果只希望方法返回一个带有匹配实体计数的 int,也可以使用 count 作为动词。

图 3.2 Spring Data 解析 repository 方法特征来确定如何运行查询语句

尽管该方法的主语是可选的,但在这里它表示 Orders。Spring Data 会忽略主题中的大多数单词,因此可以将方法命名为 readPuppiesBy… 它仍然可以找到 TacoOrder 实体,因为这是 CrudRepository 参数化的类型。

谓词跟在方法名中的 By 后面,是方法签名中最有趣的部分。在本例中,谓词引用两个 TacoOrder 属性:deliveryZip 和 placedAt。deliveryZip 属性必须与传递给方法的第一个参数的值一致。Between 关键字表示 deliveryZip 的值必须位于传入方法最后两个参数的值之间。

除了一个隐式的 Equals 操作和 Between 操作外,Spring Data 方法签名还可以包括以下任何操作:

  • IsAfter, After, IsGreaterThan, GreaterThan
  • IsGreaterThanEqual, GreaterThanEqual
  • IsBefore, Before, IsLessThan, LessThan
  • IsLessThanEqual, LessThanEqual
  • IsBetween, Between
  • IsNull, Null
  • IsNotNull, NotNull
  • IsIn, In
  • IsNotIn, NotIn
  • IsStartingWith, StartingWith, StartsWith
  • IsEndingWith, EndingWith, EndsWith
  • IsContaining, Containing, Contains
  • IsLike, Like
  • IsNotLike, NotLike
  • IsTrue, True
  • IsFalse, False
  • Is, Equals
  • IsNot, Not
  • IgnoringCase, IgnoresCase

作为 IgnoringCase 和 IgnoresCase 的替代方法,可以在方法上放置 AllIgnoringCase 或 AllIgnoresCase 来忽略所有 String 比较的大小写。例如,考虑以下方法:

List<TacoOrder> findByDeliveryToAndDeliveryCityAllIgnoresCase(
      String deliveryTo, String deliveryCity);

最后,还可以将 OrderBy 放在方法名的末尾,以便根据指定的列对结果进行排序。例如,通过 deliveryTo 属性来订购:

List<TacoOrder> findByDeliveryCityOrderByDeliveryTo(String city);

虽然命名约定对于相对简单的查询很有用,但是对于更复杂的查询,不需要太多的想象就可以看出方法名称可能会失控。在这种情况下,可以随意将方法命名为任何想要的名称,并使用 @Query 对其进行注解,以显式地指定调用方法时要执行的查询,如下例所示:

@Query("Order o where o.deliveryCity='Seattle'")
List<TacoOrder> readOrdersDeliveredInSeattle();

在这个 @Query 的简单用法中,请求在西雅图交付的所有订单。但是也可以使用 @Query 来执行几乎任何想要的查询,即使通过遵循命名约定来实现查询很困难或不可能。

自定义查询方法也适用于 Spring Data JDBC,但有两个关键区别:

  • 所有自定义查询方法都需要 @query。这是因为,与 JPA 不同,没有映射元数据以帮助 Spring Data JDBC 关联查询与方法称。
  • @Query 中指定的所有查询必须是 SQL 查询,而不是 JPA 查询。

在下一章中,我们将扩展 Spring Data 在非关系数据库中使用。当我们这样做时,您将看到自定义查询方法的工作方式非常类似,尽管查询 @Query 中使用的语言将特定于底层数据库。

results matching ""

    No results matching ""