title: 【翻譯】SpringData官方文檔第四章
date: 2016-11-27
tags:
- 翻譯
categories: 翻譯
說明:本翻譯4.6和4.7段發(fā)布在并發(fā)編程網(wǎng),其他段落為了熟悉上下文而翻譯挎袜,沒有精校袁稽。首次參與翻譯任務(wù)贩虾,翻譯的不好請(qǐng)指正由驹。
第四章 使用Spring Data Repositories
Spring Data repository abstraction目的是為了顯著的簡(jiǎn)化必要的樣板式代碼來為多種持久化數(shù)據(jù)存儲(chǔ)實(shí)現(xiàn)數(shù)據(jù)層圆到。
重要
本章解釋Spring Data repositories核心的概念和接口。Spring Data repository documentation 與 你的模塊雏亚。本章這些信息是從Spring Data Commons模塊獲取的厂财。它使用了JPA模塊的配置和代碼示范,命名引用覆蓋XML配置,它支持所有的Spring Data模塊支持的repository API痒给,Repository查詢關(guān)鍵字覆蓋被repository 接口一般的關(guān)鍵字查詢方法说墨。你的模塊特性的細(xì)節(jié)信息,查詢文檔對(duì)應(yīng)模塊苍柏。
4.1 核心概念
Spring Data repository抽象接口的核心是Repository
(可能沒有那么驚喜)尼斧。它需要管理實(shí)體類以及實(shí)體類的id作為參數(shù)。此接口主要用作是獲取要使用的類型接口并幫助擴(kuò)展這個(gè)接口试吁。
CrudRepository
為被管理的實(shí)體類提供復(fù)雜CRUD功能棺棵。
4.2 查詢方法
標(biāo)準(zhǔn)的CRUD功能repositories通常有查詢底層數(shù)據(jù)庫楼咳。
在Spring Data中,分詞聲明這些查詢變成了四個(gè)步驟的過程:
-
聲明一個(gè)接口繼承
Repository
或者它的一個(gè)子類烛恤,并且指定要被處理的實(shí)體類和Id類型母怜。interface PersonRepository extends Repository<Person, Long> { … }
-
在接口中聲明查詢方法。
interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
-
創(chuàng)建Spring生成代理實(shí)現(xiàn)上面接口缚柏,通過JavaConfig:
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config {}
或者通過XML配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> <jpa:repositories base-package="com.acme.repositories"/> </beans>
這個(gè)實(shí)例中使用了JPA命名空間苹熏。
如果你為其他存儲(chǔ)使用repository接口,你需要修改來適應(yīng)你的存儲(chǔ)模塊命名空間的聲明币喧,大概就是替換jpa為期望的轨域,例如mongodb。
當(dāng)然杀餐,注意JavaConfig并沒有明確配置一個(gè)包默認(rèn)使用注解的包干发。
為了定制包被掃描可以使用數(shù)據(jù)存儲(chǔ)注解
@Enable
的一個(gè)屬性basePackage
。
-
獲得repository實(shí)例注入并使用怜浅。
public class SomeClient { @Autowired private PersonRepository repository; public void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
以下部分詳細(xì)說明每個(gè)步驟铐然。
4.3 定義repository接口
第一步定義一個(gè)實(shí)體類依賴repository接口蔬崩。
這個(gè)接口必須繼承Repository
接口并且指定實(shí)體類型和Id類型恶座。
如果你希望實(shí)體類型擁有CRUD方法,將Repository
接口替換成繼承CrudRepository
沥阳。
4.3.1 小巧repository定義
一般情況下跨琳,你的repository接口應(yīng)該繼承Repository
,CrudRepository
或者PagingAndSortingRepository
除此之外,如果你不想繼承Spring Data接口,你也可以使用@Repository
注解定義你的接口
繼承CrudRepository
提供了一系列完整的方法來操縱你的實(shí)體.
如果你希望選擇方法,簡(jiǎn)單的從CurdRepository
復(fù)制你希望獲得的方法到你的Repository
注意
注意這允許你定義你自己的抽閑建立在Spring Data Repositories功能.
例5.有選擇的展現(xiàn)CRUD
方法
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
T findOne(ID id);
T save(T entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}
這是第一步你定義一個(gè)通用的基本接口,接口供你所有的實(shí)體repositories使用并提供findOne()
和save()
方法
這些方法會(huì)被轉(zhuǎn)發(fā)到你選擇的Spring Data提供的基本repository實(shí)現(xiàn),例如JPASimpleJpaRepository
,因?yàn)樗麄兤ヅ?code>CrudRepository的方法簽名.
因此UserPepository
現(xiàn)在可以保存用戶,查找唯一用戶根據(jù)id,并且觸發(fā)一個(gè)查詢?nèi)ゲ檎?code>Users根據(jù)他們的郵箱地址
注意
注意,中間的repository接口使用了注解
NoRepositoryBean
.對(duì)所有Spring Data在運(yùn)行時(shí)不需要生成實(shí)例的repository接口,確保你為他們添加了注解.
4.3.2 在多個(gè)Spring Data模塊使用Repositories
在你的應(yīng)用中使用唯一的Spring Data模塊,所有repository接口定義范圍限制在Spring Data模塊.
有時(shí)候應(yīng)用需要使用不止一個(gè)Spring Data模塊.
這種情況下,需要repository定義在持久化技術(shù)之間有所區(qū)別.
在class path發(fā)現(xiàn)多個(gè)repository工廠時(shí),Spring Data嚴(yán)格檢測(cè)repository配置模塊.
在repository或者實(shí)體類嚴(yán)格配置需要得細(xì)節(jié)以決定Spring Data模塊綁定一個(gè)repository的定義:
- 如果repository定義繼承模塊特殊的repository,那么對(duì)Spring Data模塊這是一個(gè)有效的備選.
-
如果實(shí)體類被模塊特有的注解類型注解,那么對(duì)Spring Data模塊這是一個(gè)有效的備選.
Spring Data模塊接收第三方注解(例如 JPA的
@Entity
)或者提供自己的注解例如@Document
為Spring Data MongoDB/Spring Data Elasticsearch.
例6.使用模塊特有的接口定義Repository
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T,ID{
…
}
interface UserRepository extends MyBaseRepository<User, Long> {
…
}
MyRepository
和UserRepository
繼承JpaRepository
在他們類型層級(jí).他們有效的表示了Spring Data JPA模塊.
例7.使用通用的接口定義Repository
.
interface AmbiguousRepository extends Repository<User, Long> {
…
}
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends
CrudRepository<T,ID> {
…
}
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
…
}
AmbiguousRepository
和AmbiguousUserRepository
在它們繼承體系里只繼承了Repository
和CrudRepository
.使用唯一的Spring Data模塊是這是完成正確的,多個(gè)模塊不能識(shí)別Repository
到底綁定哪個(gè)Spring Data.
例8.使用注解配合實(shí)體類定義Repositor
interface PersonRepository extends Repository<Person, Long> {
…
}
@Entity
public class Person {
…
}
interface UserRepository extends Repository<User, Long> {
…
}
@Document
public class User {
…
}
PersonRepository
引用使用了JPA的注解@Entity
進(jìn)行注解的Person
,所以這個(gè)repository明確的屬于Spring Data JPA.UserRepository
使用了Spring Data MongoDB的注解@Document
進(jìn)行注解.
例9.使用混合注解配合實(shí)體類定義Repositor
interface JpaPersonRepository extends Repository<Person, Long> {
…
}
interface MongoDBPersonRepository extends Repository<Person, Long> {
…
}
@Entity
@Document
public class Person {
…
}
這個(gè)示例展示實(shí)體類同時(shí)使用JPA和Spring Data MongoDB注解.
這里定義了兩個(gè)repository,JpaPersonRepository
和MongoDBPersonRepository
.
一個(gè)被JPA使用,另一個(gè)被MongoDB使用.
Spring Data不再能告訴repository區(qū)分,這將導(dǎo)致不清晰的行為
"使用模塊特有的接口定義Repository"和"使用注解配合實(shí)體類定義Repositor
"都使用了嚴(yán)格的repository配置為一個(gè)特定的Spring Data模塊識(shí)別可選的repository
使用多種持久化技術(shù)特定的注解在同一個(gè)實(shí)體類上可能在鍋中持久化技術(shù)上重用了實(shí)體類,但是這樣做Spring Data將不能確定為repository綁定唯一的模塊
最后一種方式區(qū)分repository是劃分repository的基本包.
基本包定義起始點(diǎn)是掃描repository接口定義,這意味著在合適的包定義repository.
默認(rèn)的,注解配置使用類配置的包
基于XML基本配置在這里.
例10.注解配置基本包
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }
4.4 Defining query methods
4.4 定義查詢方法
repository代理有兩種方式獲得通過方法名獲得指定查詢.可以通過方法名直接獲得查詢,或者手動(dòng)定義查詢.有可用的選項(xiàng)定義在實(shí)際的存儲(chǔ).這有一些策略決定實(shí)際查詢?nèi)绾伪粍?chuàng)建.讓我們一些看看可用的選項(xiàng).
4.4.1 Query lookup strategies
下面這些repository基本組件的決定查詢可用的策略.你可以配置策略,在XML配置中通過命名空間query-look-strategy
屬性或者在Java配置中通過啟用${store} Repository的屬性注解queryLookupStrategy
.一些策略可能不能支持特定的數(shù)據(jù)庫.
- CREATE 試圖通過方法名構(gòu)建一個(gè)指定查詢.一般處理是從方法名移除一系列給定的前綴并解析方法其他部分.更多查詢構(gòu)建信息閱讀Query creation.
- USE_DECLARED_QUERY試圖查找一個(gè)準(zhǔn)確的查詢,并在找不到時(shí)拋出一個(gè)異常.查詢可以被通過注解定義或者其它方式定義.查詢特殊存儲(chǔ)的文檔了解存儲(chǔ)的可用選擇.如果repository基本組件在啟動(dòng)時(shí)不能為方法找到一個(gè)準(zhǔn)確的查詢,將會(huì)失敗
- CREATE_IF_NOT_FOUND(默認(rèn))聯(lián)合了CREATE和USE_DECLARED_QUERY.首先查找一個(gè)準(zhǔn)確查詢,如果沒有找到,創(chuàng)建一個(gè)定制方法基于名字的查詢.這是默認(rèn)的查詢策略,因此如果你沒有做任何明確配置.它允許根據(jù)方法名快速查詢定義,而且定制查詢根據(jù)需要引入聲明的查詢.
4.4.2 查詢創(chuàng)建
建成Spring Data repository基本組件的查詢構(gòu)建機(jī)制有用的構(gòu)建了repository所有實(shí)體類的約束查詢.
這個(gè)機(jī)制分隔方法前綴find...By
,read...By
,query...By
,count...By
以及get...By
,并解析其他部分.這個(gè)引入條款可以表達(dá)包含的特性例如Distinct
,來設(shè)置明確的標(biāo)志在要生成的查詢上.然而,第一個(gè)by扮演了分解符來指明真實(shí)條件的起始.分詞在一個(gè)非惩┖保基本的水平,你可以在實(shí)體屬性上定義條件并且連接使用and或or連接他們.
例11.來自方法名的查詢創(chuàng)建
public interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String
lastname);
// Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String
firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String
firstname);
// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String
firstname);
// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
解析方法的實(shí)際結(jié)果取決于你創(chuàng)建查詢的持久化存儲(chǔ).然而,這有些問題需要注意:
- 表達(dá)式通常串行的連接屬性的遍歷與操作符.你可以使用and和or連接屬性表達(dá)式.你也可以給屬性表達(dá)式使用操作符,例如between,lessThan,granterThan,like.你可以使用的表達(dá)式操作符有between,LessThan,GreaterThan,Loke.被支持的操作符根據(jù)多樣的數(shù)據(jù)庫決定,因此查詢你引用文檔的恰當(dāng)部分.
- 方法解析支持為單獨(dú)屬性設(shè)置一個(gè)IgnoreCase標(biāo)志(例如,findByLastnameIgnoreCase(...))或者為所有屬性都支持忽略情況(通常是String情形,例如,findByLastnameAndFirstnameAllIgnoreCase(…)).忽略情況是否被支持由多樣的數(shù)據(jù)庫決定脉让,所以具體存儲(chǔ)查詢方法在引用文檔查詢相關(guān)章節(jié).
- 你可以為查詢方法引用的屬性提供靜態(tài)排序連接OrderBy子句并且提供排序方向(Asc或Desc).為創(chuàng)建一個(gè)查詢方法支持動(dòng)態(tài)排序,查看特殊參數(shù)處理.
4.4.3 屬性表達(dá)式
屬性表達(dá)式可以只為管理的實(shí)體類的直接屬性使用,就像前面所展示的那樣功炮。查詢常見時(shí)你已經(jīng)確認(rèn)解析的字段是管理的實(shí)體類的一個(gè)字段.然而,你也可以通過最近的字段定義一個(gè)約束.假設(shè)Person
的Address
有一個(gè)ZipCode
字段.這種情況一個(gè)方法如果這樣命名:
List<Person> findByAddressZipCode(ZipCode zipCode);
創(chuàng)建屬性遍歷x.address.zipCode
.決策算法從把全部(AddressZipCode
)作為屬性開始并且檢查實(shí)體類依此命名的屬性(小寫開頭).如果算法成功了,就是用這個(gè)屬性.否則,算法將源碼部分駝峰式大小寫從右側(cè)頭部到尾巴分隔,并試圖找到相應(yīng)的屬性,在我們的例子中,AddressZip
和Code
.如果從頭部找到一個(gè)屬性,算法將在這里生成樹處理后面的部分,使用描述的方式分隔.如果第一個(gè)分隔沒有匹配到,算法移動(dòng)分割點(diǎn)到左側(cè)繼續(xù)(Address
,ZipCode
).
這在大多數(shù)情況都可以使用,但也可能選擇錯(cuò)誤的屬性.假設(shè)Person
類也有一個(gè)addressZip
屬性.算法將匹配第一個(gè)匹配的,本質(zhì)上選擇了錯(cuò)誤屬性,最終失敗(伴隨的addressZip
類型沒有屬性code
).
沒了解決這種起義,你可以在方法名稱內(nèi)使用_
手動(dòng)的定義遍歷點(diǎn).所以我們的方法名稱最終像這樣:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
As we treat underscore as a reserved character we strongly advise to follow standard Java naming
conventions (i.e. not using underscores in property names but camel case instead)
我們對(duì)待下劃線當(dāng)做一個(gè)保留關(guān)鍵字,我們強(qiáng)力建議遵循標(biāo)準(zhǔn)Java命名規(guī)范(例如,使用駝峰命名而不是下劃線命名屬性名稱)
4.4.4 特殊參數(shù)處理
處理你查詢中的參數(shù),你可以定義簡(jiǎn)單的方法參數(shù)像上面的示例中.處理之外基本組件可以識(shí)別出某些特殊類型例如Pageable
和Sort
用來動(dòng)態(tài)的編碼和排序你的查詢.
例12.在查詢方法使用Pageable
,Slice
和Sort
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
第一個(gè)方法允許你在通過一個(gè)org.springframework.data.domain.Pageable
實(shí)例在查詢方法中動(dòng)態(tài)添加分頁信息在你的靜態(tài)定義查詢中.一個(gè)Page
清楚數(shù)據(jù)的全部數(shù)量和頁面總數(shù).它是通過觸發(fā)計(jì)數(shù)的基礎(chǔ)設(shè)施查詢計(jì)算總數(shù)溅潜。這個(gè)代價(jià)可能是昂貴的具體取決于所使用的存儲(chǔ),可以用Slice
代替.一個(gè)Slict
只知道下一個(gè)Slice
可到到哪里,當(dāng)運(yùn)行在一個(gè)大的結(jié)果集上時(shí)這可能已經(jīng)足夠了.排序選項(xiàng)也通過Pageable
實(shí)例處理.如果你只需要排序,簡(jiǎn)單起見添加org.springframework.data.domain.Sort
參數(shù)在你的方法.如你所見,簡(jiǎn)單返回一個(gè)列表.在這種情況下,額外元數(shù)據(jù)構(gòu)建實(shí)際的Page
實(shí)例將不會(huì)被創(chuàng)建(反過來這意味著,沒有發(fā)出額外的統(tǒng)計(jì)查詢所必須的)簡(jiǎn)單約束只在給定的范圍內(nèi)查詢.
注意
為了盡早得到你查詢了多少頁,你必須出發(fā)一個(gè)格外統(tǒng)計(jì)查詢.默認(rèn)的,這個(gè)查詢你實(shí)際觸發(fā)的查詢調(diào)用.
4.4.5 限制查詢結(jié)果
查詢方法的結(jié)果可以通過關(guān)鍵first
或者top
限制,可以交換使用.一個(gè)可選的數(shù)值可以追加在top/first來指定返回的最大結(jié)果集.如果數(shù)字缺失,假定結(jié)果集大小是1.
例13.查詢中使用Top和First限制結(jié)果大小
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
限制表達(dá)式也支持Distinct關(guān)鍵字.此外,對(duì)于將結(jié)果集設(shè)置為一個(gè)實(shí)例的查詢薪伏,支持將結(jié)果包裝到一個(gè)Optional
.如果應(yīng)用分頁或分片限制查詢分頁(并且計(jì)算可用的頁數(shù))那么這可以應(yīng)用limited結(jié)果.
注意
請(qǐng)注意滚澜,限制結(jié)果結(jié)合使用
Sort
的動(dòng)態(tài)排序的結(jié)果允許參數(shù)可以表示“k”最小的查詢方法以及“K”的查詢方法最大的元素。
4.4.6 流式處理結(jié)果
方法查詢結(jié)果可以通過使用java 8Stream<T>
逐步處理嫁怀。
特定的方法用來表示流而不是簡(jiǎn)單的包裝查詢結(jié)果在一個(gè)Stream
數(shù)據(jù)存儲(chǔ)
例14 一個(gè)用java流8Stream<T>
查詢結(jié)果
@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
注意
一個(gè)
Stream
潛在的包裝底層數(shù)據(jù)存儲(chǔ)在頁數(shù)的資源中,因此使用完畢必須關(guān)閉.你可以使用close()
方法手動(dòng)關(guān)閉Stream
或者使用一個(gè)Java7的try_with-resources塊.
try(Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(...);
}
4.4.7 異步查詢結(jié)果
Repository查詢可以使用Spring's asynchronous method execution capacity
執(zhí)行異步.這意味著方法可以一執(zhí)行立即返回,真實(shí)的查詢執(zhí)行發(fā)生在任務(wù)提交到一個(gè)Spring TaskExecutor.
@Async
Future<User> findByFirstname(String firstname);
@Async
CompletableFuture<User> findOneByFirstname(String firstname);
@Async
ListenableFuture<User> findOneByLastname(String lastname);
- 使用
java.util.concurrent.Future
作為返回類型 - 使用一個(gè)Java8
java.util.current.CompletableFuture
作為返回類型 - 使用一個(gè)
org.springframework.util.concurrent.ListenableFuture
作為返回類型
創(chuàng)建repository實(shí)例
這個(gè)章節(jié)你創(chuàng)建實(shí)例和bean定義為repository接口定義.方法之一是手動(dòng)的支持repository使用包含各個(gè)Spring Data模塊的Spring命名空間裝載,然而我們一般推薦使用Java配置的方法配置.
4.5.1 XML配置
每個(gè)Spring Data module包含一個(gè)repository 元素,這可以讓你簡(jiǎn)單的定義一個(gè)基本包,Spring為你掃描它.
例16 通過XML啟用Spring Data repositories
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<repositories base-package="com.acme.repositories" />
</beans:beans>
在前面的實(shí)例中,讓Spring掃描com.acme.repositories
包和它的子包里繼承Repository
的接口或者它的子接口.找到的每個(gè)接口,繼承組件注冊(cè)持久化技術(shù)的FactoryBean
來創(chuàng)建合適的代理處理執(zhí)行查詢方法.每個(gè)bean被注冊(cè)在一個(gè)接口名稱確定的bean name下,所以一個(gè)叫UserRepository
將注冊(cè)userRepository
.基本包屬性允許通配符,所以你可以定義一個(gè)規(guī)則掃描包.
使用過濾
默認(rèn)的基本組件選取所有在配置的基本包下繼承了持久化技術(shù)Repositpry
接口以及子接口
并且為他們創(chuàng)建一個(gè)bean實(shí)例.然而,你可能希望更細(xì)粒度的控制哪個(gè)接口bean實(shí)例被創(chuàng)建.為了實(shí)現(xiàn)這個(gè)你可以在<repository/>
中使用<include-filter/>
和<exclude-filter/>
元素.語義完全等同于Spring的上下文命名空間中的元素.更多細(xì)節(jié),查看他們的元素Spring reference documentation
例如设捐,要將某些確定的接口排除實(shí)例化為repository,可以使用以下配置:
例17. 使用exclude-filter元素
<repository base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
這個(gè)示例從待實(shí)例化對(duì)象中排除所有以SomeRepository結(jié)尾的接口.
4.5.2 JavaConfig
repository基礎(chǔ)組件也可以使用一個(gè)存儲(chǔ)的特殊的@Enable${store}Repositories
注解在一個(gè)JavaConfig類上.入門介紹Spring容器Java基本Spring容器查看文檔:JavaConfig in the Spring reference documentaional
一個(gè)簡(jiǎn)單配置啟用Spring Data repositories像這樣:
例18. repository配置基于簡(jiǎn)單注解
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
public EntityManagerFactory entityManagerFactory() {
// …
}
}
注意
示例使用了JPA特有的注解,根據(jù)你使用的存儲(chǔ)模塊決定實(shí)際如何替換.示例定義
EntityManagerFactory
bean.查閱具體存儲(chǔ)的配置
4.5.3 單獨(dú)使用
你也可以在Spring容器之外使用repository基礎(chǔ)組件,例如在CDI環(huán)境.你仍然需要一些Spring庫在你的classpath中,但是你可以以編程的方式啟動(dòng).repository包裝持久化技術(shù)支持的Spring Data模塊提供RepositoryFactory,你可以向下面這樣使用:
例19. repository工廠單獨(dú)使用
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
4.6 定制Spring Data倉庫實(shí)現(xiàn)
時(shí)常有必要為一少部分倉庫方法提供一個(gè)定制的實(shí)現(xiàn).Spring數(shù)據(jù)存儲(chǔ)庫很容易允許您提供自定義存儲(chǔ)庫代碼并將其與通用CRUD集成抽象和查詢方法功能整合.
4.6.1 為單獨(dú)倉庫添加定制行為
為了定制功能豐富一個(gè)倉庫,你首先為定制功能定義一個(gè)接口和實(shí)現(xiàn).使用你提供的倉庫接口來繼承自定義接口.
例20. 定制倉庫功能的接口
interface UserRepositoryCustom {
public void someCustomMethod(User user);
}
例21.定制功能的實(shí)現(xiàn)
class UserRepositoryImpl implements UserRepositoryCustom {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
注意
類可以被找到最重要的點(diǎn)是名字以Impl為后綴區(qū)別于倉庫的核心接口(見下文)
實(shí)現(xiàn)的本身沒有依賴Spring Data,可以是一個(gè)標(biāo)準(zhǔn)的Spring bean.所以你可以使用標(biāo)準(zhǔn)的依賴注入行為給其他bean注入引用,像JdbcTemplate
,切面的一部分等等.
例22 修改你基本的倉庫接口
interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {
// Declare query methods here
}
讓你的標(biāo)準(zhǔn)倉庫接口繼承定制的.這樣做結(jié)合了CRUD和定制功能并使其可用于客戶端.
配置
如果你使用命名空間配置,倉庫基本組件掃描類所在包,根據(jù)掃描結(jié)果嘗試自動(dòng)定制實(shí)現(xiàn).
這些類需要遵循命名規(guī)范:給倉庫接口名添加命名空間元素屬性repositoryimpl-postfix
.默認(rèn)的后綴是Impl
例23. 配置示例
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="FooBar"/>
第一個(gè)配置示例將查實(shí)查找一個(gè)類com.acme.repository.UserRepositoryImpl
來作為定制藏局實(shí)現(xiàn).然而第二個(gè)示例將嘗試查找com.acme.repository.UserRepositoryFooBar
.
手動(dòng)指定
上面的方法可以正常工作,只有當(dāng)你定制實(shí)現(xiàn)使用注解配置和自動(dòng)注入,這將與其他Spring bean一樣被對(duì)待.如果你定制的實(shí)現(xiàn)需要特殊處理,你可以像描述的簡(jiǎn)單定義一個(gè)bean并且命名它.基本組件將通過名稱引用手動(dòng)定義的bean定義而不是它自己創(chuàng)建一個(gè).
例24.手動(dòng)指定定制實(shí)現(xiàn)
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- further configuration -->
</beans:bean>
4.6.2為所有倉庫添加定制行為
當(dāng)你希望把一個(gè)單獨(dú)的方法添加到你所有的倉庫接口中時(shí),上面的方法就不可行了.為了添加定制到所有的倉庫,你首先添加一個(gè)中間接口來定義共享的行為.
例25 定義共享定制行為接口
@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID> {
void sharedCustomMethod(ID id);
}
現(xiàn)在你獨(dú)立的倉庫接口將繼承這個(gè)中間接口而不是Repository
接口來包含功能的定義.接下來創(chuàng)建一個(gè)中間接口的實(shí)現(xiàn)繼承持久化具體倉庫的基本類.這個(gè)類后面將作為倉庫代理的基本類.
例26 定制倉庫基本類
public class MyRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
private final EntityManager entityManager;
public MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced methods.
this.entityManager = entityManager;
}
public void sharedCustomMethod(ID id) {
// implementation goes here
}
}
警告
這個(gè)類需要有一個(gè)構(gòu)造方法調(diào)用父類具體存儲(chǔ)倉庫工廠實(shí)現(xiàn).萬一倉庫基礎(chǔ)類有多個(gè)構(gòu)造,覆蓋包括一個(gè)
EntityInformation
加上存儲(chǔ)具體基本組件對(duì)象(例如一個(gè)EntityManager
或者模板類)
Spring<repository/>
命名空間下的默認(rèn)行為為所有接口提供一個(gè)實(shí)現(xiàn).這意味著如果處于其當(dāng)前狀態(tài)塘淑,MyRepository
的實(shí)現(xiàn)實(shí)例將由Spring創(chuàng)建.這當(dāng)然不是被期望的,它只是作為一個(gè)用來定義實(shí)體的 Repository
和真實(shí)倉庫接口的中間接口.為了排除一個(gè)繼承Repository
的接口被當(dāng)做一個(gè)倉庫接口實(shí)例化,你可以給它使用@NoRepositoryBean
(像上面)或者把它從配置中base-package
移除.
最后一步是讓Spring Data基本組件識(shí)別到定制的倉庫基本類.在JavaConf使用注解@Enable...Repository
的屬性repositoryBaseClass
完成:
例27 使用JavaConfig配置一個(gè)定制倉庫基本類
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
類似的屬性在XML命名空間中也可以找到.
例28 使用XML配置一個(gè)定制倉庫基本類
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />
4.7 Spring Data擴(kuò)展
這部分說明Spring Data一系列的擴(kuò)展功能,可以使Spring Dta使用多樣的上下文.目前大部分集成是針對(duì)Spring MVC.
4.7.1 Querydsl擴(kuò)展
Querydsl是一個(gè)框架,通過它的流式API構(gòu)建靜態(tài)類型的SQL類查詢.
多個(gè)Spring Data模塊通過QueryDslPredicateExecutor
與Querydsl集成.
例29 QueryDslPredicateExecutor接口
public interface QueryDslPredicateExecutor<T> {
T findOne(Predicate predicate); ①
Iterable<T> findAll(Predicate predicate); ②
long count(Predicate predicate); ③
boolean exists(Predicate predicate); ④
// … more functionality omitted.
}
① 查詢并返回一個(gè)匹配Predicate
的單例實(shí)體
②查詢并返回所有匹配Predicate
的實(shí)體
③ 返回匹配Predicate
的實(shí)體數(shù)量
④ 返回是否存在一個(gè)匹配Predicate
的實(shí)體
為了簡(jiǎn)單的使用Querydsl功能,在你的倉庫接口繼承QueryDslPredicateExecutor
.
例30 在倉庫集成QueryDsl
interface UserRepository extends CrudRepository<User, Long>,
QueryDslPredicateExecutor<User> {
}
像上面這樣就可以使用Querydsl的Predicate
書寫類型安全的查詢
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);
4.7.2 Web支持
注意
本節(jié)包含Spring Data Web支持的文檔是在1.6范圍內(nèi)的Spring Data Commons實(shí)現(xiàn)的.因?yàn)橹С中乱氲膬?nèi)容改變了很多東西萝招,我們保留了舊行為的文檔在"遺留Web支持"部分.
如果模塊支持倉庫編程模型,那么Spring Data模塊附帶了各種Web模塊支持.Web關(guān)聯(lián)的東西需要Spring MVC的JAR包位于classpath路徑下,它們中有些甚至提供了Spring HATEOAS集成.一般情況,集成方式支持使用@EnableSpringDataWebSupport
注解在你的JavaConfig配置類.
例31 啟用Spring Data web支持
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}
@EnableSpringDataWebSupport
注解注冊(cè)了一些組件存捺,我們將在稍后討論.注解還將在類路徑上檢測(cè)Spring HATEOAS槐沼,如果才在將為其注冊(cè)集成組件.
作為可選項(xiàng),如果你使用XML配置,注冊(cè)SpringDataWebSupport
或者HateoasWareSpringDataWebSupport
作為Spring bean:
例32 用XML啟用Spring Data web支持
<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<!-- If you're using Spring HATEOAS as well register this one *instead* of the
former -->
<bean class= "org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
基本W(wǎng)eb支持
上面展示的的配置設(shè)置將注冊(cè)幾個(gè)基本組件:
- 一個(gè)
DomainClassConverter
啟用Spring MVC來根據(jù)請(qǐng)求參數(shù)或路徑變量管理倉例實(shí)體類的實(shí)例 -
HandlerMethodArgumentResolver
實(shí)現(xiàn)讓Spring MVC從請(qǐng)求參數(shù)解析Pageable和Sort實(shí)例.
實(shí)體類轉(zhuǎn)換
DomainClassConverter
允許你在Spring MVC控制器方法簽名中直接使用實(shí)體類型,因此你不必手動(dòng)的通過倉庫查詢實(shí)例:
例33 一個(gè)Spring MVC控制器在方法簽名中使用實(shí)體類型
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping("/{id}")
public String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
如你所見,方法直接接收一個(gè)User實(shí)例并沒有更進(jìn)一步的查詢是否必要.實(shí)例可以通過Spring MVC將路徑變量轉(zhuǎn)換為實(shí)體類的id類型并最終通過在實(shí)體類型注冊(cè)的倉庫實(shí)例上調(diào)用findOne(...)
訪問實(shí)例轉(zhuǎn)換得到.
注意
當(dāng)前的倉庫必須實(shí)現(xiàn)
CrudRepository
做好準(zhǔn)備被發(fā)現(xiàn)來進(jìn)行轉(zhuǎn)換.
為了分頁和排序分解方法參數(shù)
上面的配置片段還注冊(cè)了一個(gè)PageableHandlerMethodArgumentResolver
和一個(gè)SortHandlerMethodArgumentResolver
實(shí)例.注冊(cè)使得Pageable和Sort成為有效的控制器方法參數(shù).
例34 使用Pageable作為控制器方法參數(shù)
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired UserRepository repository;
@RequestMapping
public String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", repository.findAll(pageable));
return "users";
}
}
這個(gè)方法簽名將使Spring MVC嘗試使用下面的默認(rèn)配置從請(qǐng)求參數(shù)中轉(zhuǎn)換一個(gè)Pageable實(shí)例:
表1 請(qǐng)求參數(shù)轉(zhuǎn)換Pageable實(shí)例
參數(shù) | 說明 |
---|---|
page | 要檢索的頁面,索引為0,默認(rèn)為0 |
size | 要檢索的頁面大小,默認(rèn)20 |
sort | 被排序的屬性應(yīng)以格式property,property(, ASC|DESC) 表示.默認(rèn)生序排序,如果你希望改變排序順序,則使用多個(gè)排序參數(shù),例如?sort=firstname&sort=lastname,asc |
為了定制行為,可以繼承SpringDataWebConfiguration
或者啟用等效的HATEOAS并覆蓋pageableResolver()
或sortResolver()
方法并導(dǎo)入你的自定義配置文件替代@Enable-注解.
有一種情況你需要多個(gè)Pageable
或Sort
實(shí)例從請(qǐng)求轉(zhuǎn)換(例如處理多個(gè)表單),你可以使用Spring的@Qualifier
注解來互相區(qū)別.請(qǐng)求參數(shù)必須以${qualifier}為
前綴.這樣一個(gè)方法的簽名像這樣:
public String showUsers(Model model,
@Qualifier("foo")Pagebale first,
@Qualifier("bar") Pageable second) {
...
}
你必須填充foo_page和bar_page等.
默認(rèn)的Pageable
在方法中處理等價(jià)于一個(gè)new PageRequest(0, 20)
,但是可以使用@PageableDefaults
注解在Pageable
參數(shù)上定制.
Hypermedia支持分頁
Spring HATEOAS包裝了一個(gè)代表模型的類PageResources
,
它可以使用Page實(shí)例包裝必要的Page元數(shù)據(jù)內(nèi)容作為連接讓客戶端導(dǎo)航頁面.一個(gè)頁面到一個(gè)PageResources
的轉(zhuǎn)換被Spring HATEOAS的ResourceAssembler
接口實(shí)現(xiàn)PagedResourcesAssembler
來完成.
例35 使用一個(gè)PagedResourcesAssembler作為控制器方法參數(shù)
@Controller
class PersonController {
@Autowired PersonRepository repository;
@RequestMapping(value = "/persons", method = RequestMethod.GET)
HttpEntity<PagedResources<Person>> persons(Pageable pageable,
PagedResourcesAssembler assembler) {
Page<Person> persons = repository.findAll(pageable);
return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
}
}
像上面這樣配置將允許PageResourcesAssembler
作為控制器方法的一個(gè)參數(shù).在這調(diào)用toResources(...)方法有以下作用:
-
Page
的內(nèi)容將PageResources
實(shí)例的內(nèi)容 -
PageResources
將獲得PageMetadata
實(shí)例,該實(shí)例由Page和基礎(chǔ)的PageRequest中的信息填充 -
PageResources
獲得prev
和next
連接,添加這些依賴在頁面.這些鏈接將指向uri方法的調(diào)用映射.頁碼參數(shù)根據(jù)PageableHandlerMethodArgumentResolver
添加到參數(shù)以在后面被轉(zhuǎn)換.
假設(shè)我們有30個(gè)Person實(shí)例在數(shù)據(jù)庫.你現(xiàn)在可以觸發(fā)一個(gè)GET請(qǐng)求 http://localhost:8080/persons, 你將可以看到類似下面的內(nèi)容:
{ "links" : [ { "rel" : "next",
"href" : "http://localhost:8080/persons?page=1&size=20 }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}
你可以看到編譯生成了正確的URI,并且還會(huì)提取默認(rèn)配置轉(zhuǎn)換參數(shù)到即將到來的請(qǐng)求中的Pageable
.這意味著,如果你改變配置,鏈接也將自動(dòng)跟隨改變.默認(rèn)情況下,編譯指向控制器執(zhí)行的方法岗钩,但是這可以被一個(gè)自定義鏈接作為基本構(gòu)建來構(gòu)成分頁的Link
重載PagedResourcesAssembler.toResource(...)
方法定制.
Querydsl web 支持
那些整合了QueryDSL
的存儲(chǔ)可能從Request
查詢字符串中的屬性驅(qū)動(dòng)查詢.
這意味著前面例子的查詢字符串可以給出User
的對(duì)象
?firstname=Dave&lastname=Matthews
可以被轉(zhuǎn)換為
QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
使用了QuerydslPredicateArgumentResolver
.
注意
當(dāng)在類路徑上找到Querydsl時(shí)逸爵,該功能將在@EnableSpringDataWebSupport注解中自動(dòng)啟用
添加一個(gè)@QuerydslPredicate
到一個(gè)方法簽名將提供一個(gè)就緒的Predicate
,可以通過QueryDslPredicateExecutor
執(zhí)行.
提示
類型信息通常從返回方法上解析.由于這些信息不一定匹配實(shí)體類型,使用
QuerydslPredicate
的root
屬性可能是個(gè)好主意.
@Controller
class UserController {
@Autowired UserRepository repository;
@RequestMapping(value = "/", method = RequestMethod.GET)
String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable, @RequestParam MultiValueMap<String, String>
parameters) {
model.addAttribute("users", repository.findAll(predicate, pageable));
return "index";
}
}
為User轉(zhuǎn)換匹配查詢字符串參數(shù)的Predicate
默認(rèn)的綁定規(guī)則如下:
Object
在簡(jiǎn)單屬性上如同eq
Object
在集合作為屬性如同contains
Collection
在簡(jiǎn)單屬性上如同in
這些綁定可以通過@QuerydslPredicate
的bindings
屬性定制或者使用Java8default methods
給倉庫接口添加QuerydslBinderCustomizer
interface UserReposotory extends CurdRepository<User, String>,
QueryDslPredicateExecutor<User>,
QuerydslBinderCustomizer<QUser> {
@Override
default public void customize(QuerydslBindings bindings, QUser user) {
bindings.bind(user.username).first((path, value) -> path.contains(value));
bindings.bind(String.class).first((StringPath path, String value) ->
path.containsIgnoreCase(value));
bindings.excluding(user.password);
}
}
-
QueryDslPredicateExecutor
為Predicate
提供特殊的查詢方法提供入口 - 在倉庫接口定義
QuerydslBinderCustomizer
將自動(dòng)注解@QuerydslPredicate(bindings=...)
- 為
username
屬性定義綁定,綁定到一個(gè)簡(jiǎn)單集合 - 為
String
屬性定義默認(rèn)綁定到一個(gè)不區(qū)分大小寫的集合 - 從
Predicate
移除密碼屬性
4.7.3 倉庫填充
如果你使用Spring JDBC模塊,你可能熟悉在DataSource
使用SQL腳本來填充.一個(gè)類似的抽象在倉庫級(jí)別可以使用,盡管它不是使用SQL作為數(shù)據(jù)定義語言,因?yàn)樗仨氂纱鎯?chǔ)決定.填充根據(jù)倉庫支持XML(通過Spring的OXM抽象)和JSON(通過Jackson)定義數(shù)據(jù).
假設(shè)你有一個(gè)文件data.json
內(nèi)容如下:
例36 JSON定義的數(shù)據(jù)
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
你可以容易的根據(jù)Spring Data Commons提供倉庫的命名空間填充元素填充你的倉庫.為了填充前面的數(shù)據(jù)到你的PersonRepository,像下面這樣配置:
例37 聲明一個(gè)Jackson倉庫填充
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
http://www.springframework.org/schema/data/repository/spring-repository.xsd">
<repository:jackson2-populator locations="classpath:data.json" />
</beans>
這樣的聲明可以讓data.json
文件可以被一個(gè)Jackson的ObjectMpper
讀取和反序列化.
JSON將要解析的對(duì)象類型由檢查JSON文檔的_class
屬性決定.基本組件將最終選擇合適的倉庫去處理反序列化的對(duì)象.
要使用XML定義數(shù)據(jù)填充倉庫,你可以使用unmarshaller-populator
元素.你配置它使用Spring OXM提供給你的XML裝配選項(xiàng).在Spring reference documentation查看更多細(xì)節(jié).
例38 聲明一個(gè)裝配倉庫填充器(使用JAXB)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:repository="http://www.springframework.org/schema/data/repository"
xmlns:oxm="http://www.springframework.org/schema/oxm"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/repository
http://www.springframework.org/schema/data/repository/spring-repository.xsd
http://www.springframework.org/schema/oxm
http://www.springframework.org/schema/oxm/spring-oxm.xsd">
<repository:unmarshaller-populator locations="classpath:data.json"
unmarshaller-ref="unmarshaller" />
<oxm:jaxb2-marshaller contextPath="com.acme" />
</beans>
4.7.4 遺留web支持
Spring MVC的實(shí)體類綁定
如果正在開發(fā)Spring MVC web應(yīng)用,你通常必須從URL中解析實(shí)體類的id.默認(rèn)的,你的任務(wù)是轉(zhuǎn)化請(qǐng)求參數(shù)或URL參數(shù)到實(shí)體類并將它移交給下面或直接在實(shí)體上操作業(yè)務(wù)邏輯.這看起來像下面這樣:
@Controller
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
@Autowired
public UserController(UserRepository userRepository) {
Assert.notNull(repository, "Repository must not be null!");
this.userRepository = userRepository;
}
@RequestMapping("/{id}")
public String showUserForm(@PathVariable("id") Long id, Model model) {
// Do null check for id
User user = userRepository.findOne(id);
// Do null check for user
model.addAttribute("user", user);
return "user";
}
}
首先你為每個(gè)控制器定義一個(gè)依賴的倉庫來查找它們分別管理的實(shí)體.查詢實(shí)體也是樣板,因?yàn)樗偸且粋€(gè)findOne(...)
調(diào)用.幸運(yùn)的Spring提供了方法來注冊(cè)自定義組件,允許一個(gè)String值轉(zhuǎn)換到一個(gè)屬性類型.
屬性編輯
Spring3.0之前JavaPropertyEditors
被使用.為了集成這些,Spring Data提出一個(gè)DomainClassPropertyEditorRegistrar
來查詢所有注冊(cè)到ApplicatonContext
的Spring Data倉庫和一個(gè)定制的PropertyEditor
來管理實(shí)體類.
<bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="webBindingInitializer">
<bean class="….web.bind.support.ConfigurableWebBindingInitializer">
<property name="propertyEditorRegistrars">
<bean class=
"org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" />
</property>
</bean>
</property>
</bean>
如果你已經(jīng)像上面這樣配置Spring MVC,你可以向下面這樣配置你的控制器,從而減少不清晰和樣板式的代碼
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping("/{id}")
public String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}