# 3.2.4 自定义 JPA repository

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

```java
List<Order> findByDeliveryZip(String deliveryZip);
```

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

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

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

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

```java
List<Order> 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 方法特征来确定如何运行查询语句](https://867170852-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LrmLE3NwQoVJk02Q_BX%2F-Lrx45VtnV-WCQ6BGPcV%2F-Lrx4s9aWepQvbvNLzld%2F%E5%9B%BE%203.2.jpg?alt=media\&token=3d0567e9-de7e-40f9-a359-0bcc8cc4f6ae)

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

谓词跟在方法名中的 By 后面，是方法签名中最有趣的部分。在本例中，谓词引用两个 Order属性：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 比较的大小写。例如，考虑以下方法：

```java
List<Order> findByDeliveryToAndDeliveryCityAllIgnoresCase(
    String deliveryTo, String deliveryCity);
```

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

```java
List<Order> findByDeliveryCityOrderByDeliveryTo(String city);
```

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

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

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