12.1.4 编写 MongoDB Repository
为了理解如何使用 Spring Data MongoDB 来创建 Repository,让我们先回忆一下在第 11 章中是如何使用 Spring Data JPA 的。在程序清单 11.4 中,我们创建了一个扩展自 JpaRepository 的 SpitterRepository 接口。在那一小节中,我们还启用了 Spring Data JPA Repository 功能。这样的结果就是 Spring Data JPA 能够自动创建接口的实现,其中包括了多个内置的方法以及我们所添加的遵循命名约定的方法。
我们已经通过 @EnableMongoRepositories 注解启用了 Spring Data MongoDB 的 Repository 功能,接下来需要做的就是创建一个接口,Repository 实现要基于这个接口来生成。不过,在这里,我们不再扩展 JpaRepository,而是要扩展 MongoRepository。如下程序清单中的 OrderRepository 扩展了 MongoRepository,为 Order 文档提供了基本的 CRUD 操作。
程序清单 12.6 Spring Data MongoDB 会自动实现 Repository 接口因为 OrderRepository 扩展了 MongoRepository,因此它就会传递性地扩展 Repository 标记接口。回忆一下我们在学习 Spring Data JPA 时所了解的知识,任何扩展 Repository 的接口将会在运行时自动生成实现。在本例中,并不会实现与关系型数据库交互的 JPA Repository,而是会为 OrderRepository 生成读取和写入数据到 MongoDB 文档数据库的实现。
MongoRepository 接口有两个参数,第一个是带有 @Document 注解的对象类型,也就是该 Repository 要处理的类型。第二个参数是带有 @Id 注解的属性类型。
尽管 OrderRepository 本身并没有定义任何方法,但是它会继承多个方法,包括对 Order 文档进行 CRUD 操作的方法。表 12.2 描述了 OrderRepository 继承的所有方法。
方法 | 描述 |
long count(); | 返回指定 Repository 类型的文档数量 |
void delete(Iterable<? extends T>); | 删除与指定对象关联的所有文档 |
void delete(T); | 删除与指定对象关联的文档 |
void delete(ID); | 根据 ID 删除某一个文档 |
void deleteAll(); | 删除指定 Repository 类型的所有文档 |
boolean exists(Object); | 如果存在与指定对象相关联的文档,则返回 true |
boolean exists(ID); | 如果存在指定 ID 的文档,则返回 true |
List<T> findAll(); | 返回指定 Repository 类型的所有文档 |
List<T> findAll(Iterable<ID>); | 返回指定文档 ID 对应的所有文档 |
List findAll(Sort); | 为指定的 Repository 类型,返回排序后的所有文档列表 |
T findOne(ID); | 为指定的 ID 返回单个文档 |
Save(Iterable<s>) ; | 保存指定 Iterable 中的所有文档 |
save (s); | 为给定的对象保存一条文档 |
表 12.2 中的方法使用了传递进来和方法返回的泛型。OrderRepository 扩展了 MongoRepository,那么 T 就映射为 Order,ID 映射为 String,而 S 映射为所有扩展 Order 的类型。
添加自定义的查询方法
通常来讲,CRUD 操作是很有用的,但我们有时候可能希望 Repository 提供除内置方法以外的其他方法。
在 11.3.1 小节中,我们学习了 Spring Data JPA 支持方法命名约定,它能够帮助 Spring Data 为遵循约定的方法自动生成实现。实际上,相同的约定也适用于 Spring Data MongoDB。这意味着我们可以为 OrderRepository 添加自定义的方法:
这里我们有四个新的方法,每一个都是查找满足特定条件的 Order 对象。其中第一个用来获取 customer 属性等于传入值的 Order 列表;第二个方法获取 customer 属性 like 传入值的 Order 列表;接下来方法会返回 customer 和 type 属性等于传入值的 Order 对象;最后一个方法与前一个类似,只不过 customer 在对比的时候使用的是 like 而不是 equals。
其中,find 这个查询动词并不是固定的。如果喜欢的话,我们还可以使用 get 作为查询动词:
如果 read 更适合的话,你还可以使用这个动词:
除此之外,还有一个特殊的动词用来为匹配的对象计数:
与 Spring Data JPA 类似,在查询动词与 By 之前,我们有很大的灵活性。例如,我们可以标示要查找什么内容:
其中,Orders 这个词没并没有什么特殊之处,它不会影响要获取的内容。我们也可以将方法按照如下的方式命名:
其实,并不是必须要返回 List<Order>,如果只想要一个 Order 对象的话,我们可以只需简单地返回 Order:
这里,所返回的就是原本 List 中的第一个 Order 对象。如果没有匹配元素的话,方法将会返回 null。
指定查询
在 11.3.2 小节中,@Query 注解可以为 Repository 方法指定自定义的查询。@Query 能够像在 JPA 中那样用在 MongoDB 上。唯一的区别在于针对 MongoDB 时,@Query 会接受一个 JSON 查询,而不是 JPA 查询。
例如,假设我们想要查询给定类型的订单,并且要求 customer 的名称为 “Chuck Wagon”。OrderRepository 中如下的方法声明能够完成所需的任务:
@Query 中给定的 JSON 将会与所有的 Order 文档进行匹配,并返回匹配的文档。需要注意的是,type 属性映射成了 “?0”,这表明 type 属性应该与查询方法的第零个参数相等。如果有多个参数的话,它们可以通过 “?1”、“?2” 等方式进行引用。
混合自定义的功能
在 11.3.3 小节中,我们学习了如何将完全自定义的方法混合到自动生成的 Repository 中。对于 JPA 来说,这还涉及到创建一个中间接口来声明自定义的方法,为这些自定义方法创建实现类并修改自动化的 Repository 接口,使其扩展中间接口。对于 Spring Data MongoDB 来说,这些步骤都是相同的。
假设我们想要查询文档中 type 属性匹配给定值的 Order 对象。我们可以通过创建签名为 List<Order> findByType(String t) 的方法,很容易实现这个功能。但是,如果给定的类型是 “NET”,那我们就查找 type 值为 “WEB” 的 Order 对象。要实现这个功能的话,这就有些困难了,即便使用 @Query 注解也不容易实现。不过,混合实现的做法能够完成这项任务。
首先,定义中间接口:
这非常简单。接下来,我们要编写混合实现,具体实现如下面的程序清单所示。
可以看到,混合实现中注入了 MongoOperations(也就是 MongoTemplate 所实现的接口)。findOrdersByType() 方法使用 MongoOperations 对数据库进行了查询,查找匹配条件的文档。
剩下的工作就是修改 OrderRepository,让其扩展中间接口 OrderOperations:
将这些关联起来的关键点在于实现类的名称为 OrderRepositoryImpl。这个名字前半部分与 OrderRepository 相同,只是添加了 “Impl” 后缀。当 Spring Data MongoDB 生成 Repository 实现时,它会查找这个类并将其混合到自动生成的实现中。
如果你不喜欢 “Impl” 后缀的话,那么可以配置 Spring Data MongoDB,让其按照名字查找具备不同后缀的类。我们需要做的就是设置 @EnableMongoRepositories 的属性(在 Spring 配置类中):
如果使用 XML 配置的话,我们可以设置的 repository-impl-postfix 属性:
不管采用哪种方式,我们现在都让 Spring Data MongoDB 查找名为 OrderRepositoryStuff 的类,而不再查找 OrderRepositoryImpl。
像 MongoDB 这样的文档数据库能够解决特定类型的问题,但是就像关系型数据库不是全能型数据库那样,MongoDB 同样如此。有些问题并不是关系型数据库或文档型数据库适合解决的,不过,幸好我们的选择并不仅限于这两种。
接下来,我们看一下 Spring Data 如何支持 Neo4j,这是一种很流行的图数据库。
Last updated