本节书摘来自异步社区《Spring Data实战》一书中的第2章,第2.3节,作者: 【美】Mark Pollack , Oliver Gierke , Thomas Risberg , Jon Brisbin , Michael Hunger著,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.3 定义Repository
到目前为止,我们看到了带有查询方法的Repository接口,这些查询有的是从方法名中衍生出来的,有的是手动声明的,这取决于Spring Data为实际存储类型所提供的使用方式。为了衍生出这些查询,我们必须扩展Spring Data的特定标识接口:Repository。除了查询以外,在你的Repository中还需要一些其他的功能:存储对象,删除对象,根据ID进行查找,返回所有存储的实体或按页对它们进行访问。通过Repository接口来暴露这些功能的最简单方式就是使用一个Spring Data所提供的更为高级的Repository接口。
Repository
一个简单的标识接口,允许Spring Data的基础设施获取用户定义的Repository。
CrudRepository
扩展自Repository并添加了基本的持久化方法如对实体的保存、查找以及删除。
PagingAndSortingRepositories
扩展自CrudRepository并添加了按页访问实体以及根据给定的条件(criteria)进行排序的方法。
假设我们想让CustomerRepository暴露基本的CRUD方法,所需要做就是修改其声明,如示例2-12所示。
示例2-12 暴露CRUD方法的CustomerRepository
CrudRepository接口如示例2-13所示。它包括了保存单个实体以及多个Iterable实体的方法、获取单个实体或所有实体的方法以及不同形式的delete(...)方法。
示例2-13 CrudRepository
支持Repository方式的每个Spring Data模块都提供了这个接口的实现。因此,我们声明的命名空间元素会触发基础设施,这些设施不仅会启动那些用于执行查询方法的合适代码,同时还会使用一个通用Repository实现类的实例来在背后执行CrudRepository中所声明的方法,最终会将save(...)、findAll()等方法的调用委托给该实例。PagingAndSortingRepository(如示例2-14所示)扩展了CrudRepository并为通用的findAll(...)添加了处理Pageable和Sort实例的方法,从而能够实现逐页访问实体。
示例2-14 PagingAndSortingRepository
要将这些功能引入到CustomerRepository中,只需简单地扩展PagingAndSorting Repository来取代CrudRepository即可。
2.3.1 调整Repository接口
正如我们在前面所见,通过扩展合适的Spring Data接口,可以很容易地引入大量预先定义的功能。这种级别的粒度实际上是一种权衡,那就是如果为所有的查找方法、所有的保存方法等都定义单独的接口,我们会暴露接口的数量(以及因此导致的复杂性)以及开发人员使用的便利性之间的权衡。
但是,可能会有这样的场景,那就是只想暴露读方法(CRUD中的R)或者只想在Repository接口中将删除方法屏蔽掉。如今,Spring Data允许定义个性化的基础Repository,只需按照以下的步骤操作即可。
1.创建一个接口,这个接口要么扩展自Repository,要么添加@RepositoryDefinition注解。
2.添加想要暴露的方法并确保它们与Spring Data基础Repository接口所提供的方法签名相同。
3.对于实体所对应的接口声明,要使用这个接口作为基础接口。
为了阐述这一点,假设我们只想暴露接收Pageable的findAll(...)方法以及save方法。这个基础接口看起来可能如示例2-15所示。
示例2-15 自定义基础Repository接口
需要注意的一点是我们为这个接口添加了一个额外的注解@NoRepositoryBean,从而确保Spring Data Repository的基础设施不会试图为其创建Bean的实例。让CustomerRepository扩展这个接口就能精确做到只暴露你所定义的API。
接下来可以定义出各种基本的接口(如ReadOnlyRepository或SaveOnlyRepository)甚至组成它们的继承体系,这取决于项目的需要。通常建议本地定义的CRUD方法在开始的时候直接位于每个实体的具体Repository中,必要的话,再将它们要么转移到Spring Data提供的基础Repository中,要么转移到特制的Repository中。按照这种方式,可以保证随着项目复杂性的增长,构件(artifact)的数量能够自然地增长。
2.3.2 手动实现Repository方法
到目前为止,看到了两种类型的Repository方法:CRUD方法和查询方法。每种类型都是由Spring Data的基础设施实现的,要么通过背后的实现类,要么通过查询执行引擎。当构建应用程序的时候,这两种场景可能会覆盖你所面临的很大范围的数据访问操作。但是,有些场景需要手动实现代码。现在,让我们看一下如何做到这一点。
我们开始只实现那些需要手动实现的功能并在实现类中遵循一些命名的约定,如示例2-16所示。
示例2-16 为Repository实现自定义功能
接口和实现类均不需要了解Spring Data的任何事情。它与使用Spring手动实现代码非常类似。按照Spring Data来看,这个代码片段最有意思的地方在于实现类的名字遵循了命名的约定,也就是在核心Repository接口(在我们的场景中就是CustomerRepository)的名字上加Impl后缀。同时需要注意,我们将接口和实现类都设为包内私有(package private),从而阻止从包外访问它们。
最后一步是修改初始Repository接口的声明,使其扩展刚刚引入的接口,如示例2-17所示。
示例2-17 在CustomerRepository中包含自定义功能
现在,我们已经将CustomerRepositoryCustom暴露的API引入到CustomerRepository之中了,这会使其成为Customer数据访问API的中心点。客户端代码现在就可以调用CustomerRepository.myCustomMethod(...)了。但是,这个实现类会如何被发现并置于最终执行的代理之中的呢?实际上,Repository的启动过程看起来是这样的。
1.发现repository接口(如CustomerRepository)。
2.尝试寻找一个Bean定义,这个Bean的名字为接口的小写形式并添加Impl后缀(如customerRepositoryImpl)。如果能够找到,就使用它。
3.如果没有找到,我们会扫描寻找一个类,这个类的名字为核心Repository接口的名字并添加Impl后缀(例如,在这个例子中CustomerRepositoryImpl会被找到)。如果找到了这样的类,那么将其注册为Spring Bean并使用它。
4.找到的自定义实现类将会装配到被发现接口的代理配置之中并且在方法调用时会作为潜在的目标类。
这种机制可以很容易地为特定Repository实现自定义代码。用于进行实现查找的后缀可以在XML命名空间中或启用Repository的注解属性中(查看各种存储相关的章节来了解更多)进行个性化设置。