© 2008-2014 原始作者。
在您未對此類副本收取任何費用的前提下,且在印刷或電子散布的每一份副本皆包含此著作權聲明的前提下,本文件的副本可供您個人使用及散布給他人。 |
序言
參考文件
2. 使用 Spring Data Repositories
Spring Data repository 抽象化的目標是大幅減少實作各種持久性儲存的資料存取層所需的樣板程式碼數量。
Spring Data repository 文件和您的模組 本章說明 Spring Data repositories 的核心概念和介面。本章中的資訊取自 Spring Data Commons 模組。它使用 Java Persistence API (JPA) 模組的配置和程式碼範例。調整 XML 命名空間宣告和要擴展的類型,以符合您正在使用的特定模組的等效項。命名空間參考涵蓋所有支援 repository API 的 Spring Data 模組皆支援的 XML 配置,Repository 查詢關鍵字涵蓋 repository 抽象化通常支援的查詢方法關鍵字。如需有關您的模組特定功能的詳細資訊,請查閱本文檔中關於該模組的章節。 |
2.1. 核心概念
Spring Data repository 抽象化的中心介面是 Repository
(可能不會太令人驚訝)。它採用要管理的網域類別以及網域類別的 ID 類型作為類型參數。此介面主要充當標記介面,以捕獲要使用的類型,並幫助您發現擴展此介面的介面。CrudRepository
為正在管理的實體類別提供複雜的 CRUD 功能。
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity); (1)
T findOne(ID primaryKey); (2)
Iterable<T> findAll(); (3)
Long count(); (4)
void delete(T entity); (5)
boolean exists(ID primaryKey); (6)
// … more functionality omitted.
}
1 | 儲存給定的實體。 |
2 | 傳回由給定 ID 識別的實體。 |
3 | 傳回所有實體。 |
4 | 傳回實體的數量。 |
5 | 刪除給定的實體。 |
6 | 指示是否已存在具有給定 ID 的實體。 |
我們也提供特定於持久性技術的抽象化,例如 JpaRepository 或 MongoRepository 。這些介面擴展了 CrudRepository ,除了更通用的與持久性技術無關的介面(例如 CrudRepository)之外,還公開了底層持久性技術的功能。 |
在 CrudRepository
之上,有一個 PagingAndSortingRepository
抽象化,它新增了額外的方法來簡化對實體的分頁存取
public interface PagingAndSortingRepository<T, ID extends Serializable>
extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
存取 User
的第二頁,頁面大小為 20,您可以簡單地執行以下操作
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));
除了查詢方法外,還可以使用查詢推導來進行計數和刪除查詢。
public interface UserRepository extends CrudRepository<User, Long> {
Long countByLastname(String lastname);
}
public interface UserRepository extends CrudRepository<User, Long> {
Long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
2.2. 查詢方法
標準 CRUD 功能 repositories 通常在底層資料儲存上有查詢。使用 Spring Data,宣告這些查詢變成一個四步驟流程
-
宣告一個擴展 Repository 或其子介面之一的介面,並將其類型設定為它將處理的網域類別和 ID 類型。
interface PersonRepository extends Repository<User, Long> { … }
-
在介面上宣告查詢方法。
interface PersonRepository extends Repository<User, Long> { List<Person> findByLastname(String lastname); }
-
設定 Spring 以為這些介面建立代理實例。透過 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>
本範例中使用 JPA 命名空間。如果您正在為任何其他儲存使用 repository 抽象化,則需要將其變更為儲存模組的適當命名空間宣告,該宣告應將
jpa
交換為例如mongodb
。另請注意,JavaConfig 變體不會明確配置套件,因為預設情況下會使用註解類別的套件。若要自訂要掃描的套件 -
取得注入的 repository 實例並使用它。
public class SomeClient { @Autowired private PersonRepository repository; public void doSomething() { List<Person> persons = repository.findByLastname("Matthews"); } }
以下各節詳細說明每個步驟。
2.3. 定義 repository 介面
第一步,您定義一個特定於網域類別的 repository 介面。該介面必須擴展 Repository 並設定為網域類別和 ID 類型。如果您想要公開該網域類型的 CRUD 方法,請擴展 CrudRepository
而不是 Repository
。
2.3.1. 微調 repository 定義
通常,您的 repository 介面將擴展 Repository
、CrudRepository
或 PagingAndSortingRepository
。或者,如果您不想擴展 Spring Data 介面,您也可以使用 @RepositoryDefinition
註解您的 repository 介面。擴展 CrudRepository
會公開一整套方法來操作您的實體。如果您希望選擇性地公開要公開的方法,只需將您想要公開的方法從 CrudRepository
複製到您的網域 repository 中即可。
這允許您在提供的 Spring Data Repositories 功能之上定義您自己的抽象化。 |
@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);
}
在第一步中,您為所有網域 repositories 定義了一個通用的基礎介面,並公開了 findOne(…)
以及 save(…)
。這些方法將被路由到您選擇的儲存的基礎 repository 實作中,由 Spring Data 提供,例如,如果是 JPA SimpleJpaRepository
,因為它們符合 CrudRepository
中的方法簽章。因此,UserRepository
現在將能夠儲存使用者,並依 ID 尋找單個使用者,以及觸發查詢以依其電子郵件地址尋找 Users
。
請注意,中繼 repository 介面已使用 @NoRepositoryBean 註解。請確保將該註解新增到所有 Spring Data 不應在執行階段為其建立實例的 repository 介面。 |
2.4. 定義查詢方法
repository 代理有兩種方法可以從方法名稱推導出特定於儲存的查詢。它可以直接從方法名稱推導查詢,或使用手動定義的查詢。可用選項取決於實際的儲存。但是,必須有一種策略來決定要建立的實際查詢。讓我們看看可用的選項。
2.4.1. 查詢查找策略
以下策略可用於 repository 基礎架構以解析查詢。您可以使用命名空間在命名空間中配置策略,透過 XML 配置的情況下使用 query-lookup-strategy
屬性,或在 Java 配置的情況下透過 Enable${store}Repositories 註解的 queryLookupStrategy
屬性。某些策略可能不支援特定的資料儲存。
-
CREATE
嘗試從查詢方法名稱建構特定於儲存的查詢。一般方法是從方法名稱中移除一組給定的眾所周知的字首,並剖析方法的其餘部分。在查詢建立中閱讀有關查詢建構的更多資訊。 -
USE_DECLARED_QUERY
嘗試尋找宣告的查詢,如果找不到,則會擲回例外狀況。查詢可以透過某處的註解定義,或透過其他方式宣告。請查閱特定儲存的文件,以尋找該儲存的可用選項。如果 repository 基礎架構在啟動時間找不到該方法的宣告查詢,則會失敗。 -
CREATE_IF_NOT_FOUND
(預設)結合了CREATE
和USE_DECLARED_QUERY
。它首先查找宣告的查詢,如果找不到宣告的查詢,則會建立自訂的基於方法名稱的查詢。這是預設的查找策略,因此,如果您未明確配置任何內容,則將使用它。它允許透過方法名稱快速定義查詢,但也允許透過根據需要引入宣告的查詢來自訂調整這些查詢。
2.4.2. 查詢建立
內建於 Spring Data repository 基礎架構中的查詢建構器機制對於在 repository 的實體上建構約束查詢非常有用。該機制會從方法中剝離字首 find…By
、read…By
、query…By
、count…By
和 get…By
,並開始剖析其餘部分。引入子句可以包含其他表達式,例如 Distinct
,以在要建立的查詢上設定 distinct 標誌。但是,第一個 By
充當分隔符,以指示實際條件的開始。在非常基礎的層級上,您可以定義實體屬性上的條件,並使用 And
和 Or
將它們串連起來。
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);
}
剖析方法的實際結果取決於您在其中建立查詢的持久性儲存。但是,有一些一般事項需要注意。
-
表達式通常是屬性遍歷與可以串連的運算符的組合。您可以將屬性表達式與
AND
和OR
結合使用。您還可以獲得對屬性表達式的運算符的支援,例如Between
、LessThan
、GreaterThan
、Like
。支援的運算符可能因資料儲存而異,因此請查閱參考文件中相應的部分。 -
方法剖析器支援為個別屬性(例如,
findByLastnameIgnoreCase(…)
)或針對支援忽略大小寫的類型的所有屬性(通常是String
實例,例如,findByLastnameAndFirstnameAllIgnoreCase(…)
)設定IgnoreCase
標誌。是否支援忽略大小寫可能因儲存而異,因此請查閱參考文件中特定於儲存的查詢方法中的相關章節。 -
您可以透過將
OrderBy
子句附加到引用屬性的查詢方法並提供排序方向(Asc
或Desc
)來應用靜態排序。若要建立支援動態排序的查詢方法,請參閱特殊參數處理。
2.4.3. 屬性表達式
屬性表達式只能參考受管理實體的直接屬性,如前面的範例所示。在查詢建立時,您已經確保剖析的屬性是受管理網域類別的屬性。但是,您也可以透過遍歷巢狀屬性來定義條件約束。假設 Person
具有帶有 ZipCode
的 Address
。在這種情況下,方法名稱為
List<Person> findByAddressZipCode(ZipCode zipCode);
建立屬性遍歷 x.address.zipCode
。解析演算法首先將整個部分 (AddressZipCode
) 解釋為屬性,並檢查網域類別中是否有名稱(未大寫)與該名稱相同的屬性。如果演算法成功,則會使用該屬性。如果沒有,則演算法會從右側在駝峰式部分分割來源,分為頭部和尾部,並嘗試尋找對應的屬性,在本範例中為 AddressZip
和 Code
。如果演算法找到具有該頭部的屬性,則會取得尾部並從那裡繼續向下建構樹狀結構,並以上述方式分割尾部。如果第一次分割不符合,則演算法會將分割點向左移動 (Address
, ZipCode
) 並繼續。
雖然這應該適用於大多數情況,但演算法可能會選取錯誤的屬性。假設 Person
類別也具有 addressZip
屬性。演算法將在第一個分割回合中已經匹配,並且基本上會選取錯誤的屬性並最終失敗(因為 addressZip
的類型可能沒有 code
屬性)。
若要解決此歧義,您可以在方法名稱內使用 _
來手動定義遍歷點。因此,我們的方法名稱最終會像這樣
List<Person> findByAddress_ZipCode(ZipCode zipCode);
如果您的屬性名稱包含底線 (例如 first_name
),您可以使用第二個底線來逸出方法名稱中的底線。對於 first_name
屬性,查詢方法必須命名為 findByFirst__name(…)
。
2.4.4. 特殊參數處理
若要在查詢中處理參數,您只需定義方法參數,如上面的範例所示。除此之外,基礎架構還會辨識某些特定類型,例如 Pageable
和 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);
第一個方法允許您將 org.springframework.data.domain.Pageable
實例傳遞給查詢方法,以將分頁動態新增到您的靜態定義查詢。Page
知道可用元素的總數和頁數。它透過基礎架構觸發計數查詢來計算總數。由於這可能很昂貴,具體取決於使用的儲存,因此可以使用 Slice
作為傳回值。Slice
只知道是否有下一個可用的 Slice
,當瀏覽較大的結果集時,這可能就足夠了。
排序選項也透過 Pageable
實例處理。如果您只需要排序,只需將 org.springframework.data.domain.Sort
參數新增到您的方法即可。正如您也可以看到的,也可以簡單地傳回 List
。在這種情況下,將不會建立建構實際 Page
實例所需的其他元數據(這反過來表示將不會發出必要的額外計數查詢),而是僅將查詢限制為僅查找給定範圍的實體。
若要找出查詢的頁數,您必須觸發額外的計數查詢。預設情況下,此查詢將從您實際觸發的查詢中推導出來。 |
2.5. 建立 repository 實例
在本節中,您將為定義的 repository 介面建立實例和 Bean 定義。一種方法是使用 Spring 命名空間,該命名空間與每個支援 repository 機制的 Spring Data 模組一起提供,儘管我們通常建議使用 Java-Config 樣式的配置。
2.5.1. XML 配置
每個 Spring Data 模組都包含一個 repositories 元素,允許您簡單地定義 Spring 為您掃描的基礎套件。
<?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>
在前面的範例中,指示 Spring 掃描 com.acme.repositories
及其所有子套件,以尋找擴展 Repository
或其子介面之一的介面。對於找到的每個介面,基礎架構都會註冊特定於持久性技術的 FactoryBean
,以建立處理查詢方法調用的適當代理。每個 Bean 都以從介面名稱衍生的 Bean 名稱註冊,因此 UserRepository
的介面將在 userRepository
下註冊。base-package
屬性允許萬用字元,因此您可以定義掃描套件的模式。
使用篩選器
預設情況下,基礎架構會選取擴展配置基礎套件下特定於持久性技術的 Repository
子介面的每個介面,並為其建立 Bean 實例。但是,您可能希望更精細地控制為哪些介面建立 Bean 實例。若要執行此操作,請在 <repositories />
內使用 <include-filter />
和 <exclude-filter />
元素。語意與 Spring 的 context 命名空間中的元素完全等效。如需詳細資訊,請參閱關於這些元素的Spring 參考文件。
例如,若要從實例化為 repository 中排除某些介面,您可以使用以下配置
<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
此範例排除所有以 SomeRepository
結尾的介面被實例化。
2.5.2. JavaConfig
也可以使用 JavaConfig 類別上特定於儲存的 @Enable${store}Repositories
註解來觸發 repository 基礎架構。如需 Spring 容器的基於 Java 的配置的簡介,請參閱參考文件。[1]
啟用 Spring Data repositories 的範例配置如下所示。
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
public EntityManagerFactory entityManagerFactory() {
// …
}
}
範例使用特定於 JPA 的註解,您將根據您實際使用的儲存模組變更該註解。這同樣適用於 EntityManagerFactory Bean 的定義。請查閱涵蓋特定於儲存的配置的章節。 |
2.5.3. 獨立使用
您也可以在 Spring 容器外部使用 repository 基礎架構,例如在 CDI 環境中。您仍然需要在類別路徑中使用一些 Spring 程式庫,但通常您也可以以程式設計方式設定 repositories。提供 repository 支援的 Spring Data 模組隨附特定於持久性技術的 RepositoryFactory,您可以按如下所示使用它。
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);
2.6. Spring Data repositories 的自訂實作
通常,需要為一些 repository 方法提供自訂實作。Spring Data repositories 可讓您輕鬆提供自訂 repository 程式碼,並將其與通用 CRUD 抽象化和查詢方法功能整合。
2.6.1. 為單個 repositories 新增自訂行為
若要使用自訂功能豐富 repository,您首先要為自訂功能定義介面和實作。使用您提供的 repository 介面來擴展自訂介面。
interface UserRepositoryCustom {
public void someCustomMethod(User user);
}
class UserRepositoryImpl implements UserRepositoryCustom {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
要找到的類別的最重要位元是與核心 repository 介面相比,其名稱的 Impl 後綴(請參閱下文)。 |
實作本身不依賴 Spring Data,並且可以是常規 Spring Bean。因此,您可以使用標準的依賴注入行為來注入對其他 Bean(例如 JdbTemplate)的參考,參與方面等等。
interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {
// Declare query methods here
}
讓您的標準 repository 介面擴展自訂介面。這樣做會結合 CRUD 和自訂功能,並使其可供用戶端使用。
配置
如果您使用命名空間配置,repository 基礎架構會嘗試透過掃描我們在其中找到 repository 的套件下方的類別來自動偵測自訂實作。這些類別需要遵循命名慣例,將命名空間元素的屬性 repository-impl-postfix
附加到找到的 repository 介面名稱。此後綴預設為 Impl
。
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="FooBar" />
第一個配置範例將嘗試查找類別 com.acme.repository.UserRepositoryImpl
以充當自訂 repository 實作,而第二個範例將嘗試查找 com.acme.repository.UserRepositoryFooBar
。
手動連線
如果您的自訂實作僅使用基於註解的配置和自動連線,則剛剛顯示的方法非常有效,因為它將被視為任何其他 Spring Bean。如果您的自訂實作 Bean 需要特殊連線,您只需宣告 Bean 並根據剛剛描述的慣例命名即可。然後,基礎架構將依名稱參考手動定義的 Bean 定義,而不是自行建立一個。
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- further configuration -->
</beans:bean>
2.6.2. 為所有 repositories 新增自訂行為
當您想要將單個方法新增到所有 repository 介面時,上述方法不可行。
-
若要為所有 repositories 新增自訂行為,您首先要新增一個中繼介面來宣告共用行為。
範例 17. 宣告自訂共用行為的介面public interface MyRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { void sharedCustomMethod(ID id); }
-
現在,您的個別 repository 介面將擴展此中繼介面,而不是 Repository 介面,以包含宣告的功能。
-
接下來,建立中繼介面的實作,該實作擴展了特定於持久性技術的 repository 基底類別。然後,此類別將充當 repository 代理的自訂基底類別。
範例 18. 自訂 repository 基底類別public class MyRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> { private EntityManager entityManager; // There are two constructors to choose from, either can be used. public MyRepositoryImpl(Class<T> domainClass, EntityManager entityManager) { super(domainClass, entityManager); // This is the recommended method for accessing inherited class dependencies. this.entityManager = entityManager; } public void sharedCustomMethod(ID id) { // implementation goes here } }
Spring
<repositories />
命名空間的預設行為是為屬於base-package
的所有介面提供實作。這表示,如果保持目前狀態,Spring 將建立 MyRepository 的實作實例。當然,這不是理想的,因為它應該僅充當 Repository 和您想要為每個實體定義的實際 repository 介面之間的中介。若要從實例化為 repository 實例中排除擴展 Repository 的介面,您可以透過使用 @NoRepositoryBean 註解它,或將其移出配置的base-package
。 -
然後建立自訂 repository factory 以取代預設的 RepositoryFactoryBean,而 RepositoryFactoryBean 又將產生自訂的 RepositoryFactory。然後,新的 repository factory 將提供您的 MyRepositoryImpl 作為擴展 Repository 介面的任何介面的實作,從而取代您剛剛擴展的 SimpleJpaRepository 實作。
範例 19. 自訂 repository factory Beanpublic class MyRepositoryFactoryBean<R extends JpaRepository<T, I>, T, I extends Serializable> extends JpaRepositoryFactoryBean<R, T, I> { protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { return new MyRepositoryFactory(entityManager); } private static class MyRepositoryFactory<T, I extends Serializable> extends JpaRepositoryFactory { private EntityManager entityManager; public MyRepositoryFactory(EntityManager entityManager) { super(entityManager); this.entityManager = entityManager; } protected Object getTargetRepository(RepositoryMetadata metadata) { return new MyRepositoryImpl<T, I>((Class<T>) metadata.getDomainClass(), entityManager); } protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { // The RepositoryMetadata can be safely ignored, it is used by the JpaRepositoryFactory //to check for QueryDslJpaRepository's which is out of scope. return MyRepository.class; } } }
-
最後,直接宣告自訂 factory 的 Bean,或使用 Spring 命名空間的
factory-class
屬性來告知 repository 基礎架構使用您的自訂 factory 實作。範例 20. 將自訂 factory 與命名空間搭配使用<repositories base-package="com.acme.repository" factory-class="com.acme.MyRepositoryFactoryBean" />
2.7. Spring Data 擴展
本節記錄了一組 Spring Data 擴展,這些擴展可以在各種環境中啟用 Spring Data 使用。目前,大多數整合都以 Spring MVC 為目標。
2.7.1. Web 支援
本節包含 Spring Data Web 支援的文件,因為它是在 1.6 範圍內的 Spring Data Commons 中實作的。由於新引入的支援變更了很多內容,因此我們將先前行為的文件保留在舊版 Web 支援中。 |
如果模組支援 repository 程式設計模型,Spring Data 模組會隨附各種 Web 支援。Web 相關內容需要在類別路徑中使用 Spring MVC JAR,其中一些甚至提供與 Spring HATEOAS 的整合 [2]。一般而言,透過在 JavaConfig 配置類別中使用 @EnableSpringDataWebSupport
註解來啟用整合支援。
@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration { }
@EnableSpringDataWebSupport
註解註冊了一些我們稍後將討論的元件。如果存在,它還將偵測類別路徑上的 Spring HATEOAS 並為其註冊整合元件。
或者,如果您使用的是 XML 配置,請將 SpringDataWebSupport
或 HateoasAwareSpringDataWebSupport
註冊為 Spring Bean
<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" />
基本 Web 支援
上面顯示的配置設定將註冊一些基本元件
-
DomainClassConverter
,以啟用 Spring MVC 從請求參數或路徑變數解析 repository 管理的網域類別的實例。 -
HandlerMethodArgumentResolver
實作,以允許 Spring MVC 從請求參數解析 Pageable 和 Sort 實例。
DomainClassConverter
DomainClassConverter
允許您直接在 Spring MVC 控制器方法簽章中使用網域類型,因此您不必透過 repository 手動查找實例
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping("/{id}")
public String showUserForm(@PathVariable("id") User user, Model model) {
model.addAttribute("user", user);
return "userForm";
}
}
正如您所見,該方法直接接收 User 實例,而無需進一步查找。可以透過讓 Spring MVC 首先將路徑變數轉換為網域類別的 ID 類型,並最終透過在為網域類型註冊的 repository 實例上呼叫 findOne(…)
來存取實例,從而解析實例。
目前,repository 必須實作 CrudRepository 才能符合被發現用於轉換的條件。 |
Pageable 和 Sort 的 HandlerMethodArgumentResolvers
上面的配置程式碼片段還註冊了 PageableHandlerMethodArgumentResolver
以及 SortHandlerMethodArgumentResolver
的實例。註冊啟用 Pageable
和 Sort
作為有效控制器方法引數
@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";
}
}
此方法簽章將導致 Spring MVC 嘗試使用以下預設配置從請求參數推導 Pageable 實例
|
您想要檢索的頁面。 |
|
您想要檢索的頁面大小。 |
|
應排序的屬性,格式為 |
若要自訂此行為,請擴展 SpringDataWebConfiguration
或啟用 HATEOAS 的等效項,並覆寫 pageableResolver()
或 sortResolver()
方法,並匯入您的自訂配置檔案,而不是使用 @Enable
註解。
如果您需要從請求中解析多個 Pageable
或 Sort
實例(例如,用於多個表格),您可以使用 Spring 的 @Qualifier
註解來區分彼此。然後,請求參數必須以 ${qualifier}_
為字首。因此,對於像這樣的方法簽章
public String showUsers(Model model,
@Qualifier("foo") Pageable first,
@Qualifier("bar") Pageable second) { … }
您必須填入 foo_page
和 bar_page
等。
傳遞到方法中的預設 Pageable
等效於 new PageRequest(0, 20)
,但可以使用 Pageable
參數上的 @PageableDefaults
註解來自訂。
Pageables 的超媒體支援
Spring HATEOAS 隨附表示模型類別 PagedResources
,該類別允許使用必要的 Page
元數據以及連結來豐富 Page
實例的內容,以讓用戶端輕鬆瀏覽頁面。Page 到 PagedResources
的轉換由 Spring HATEOAS ResourceAssembler
介面的實作 PagedResourcesAssembler
完成。
@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);
}
}
啟用如上所示的配置允許將 PagedResourcesAssembler
用作控制器方法引數。在其上呼叫 toResources(…)
將導致以下情況
-
Page
的內容將成為PagedResources
實例的內容。 -
PagedResources
將取得附加的PageMetadata
實例,其中填入了來自Page
和底層PageRequest
的資訊。 -
PagedResources
根據頁面的狀態取得附加的prev
和next
連結。連結將指向方法調用對應到的 URI。新增至方法的頁碼參數將符合PageableHandlerMethodArgumentResolver
的設定,以確保稍後可以解析連結。
假設我們的資料庫中有 30 個 Person 實例。您現在可以觸發請求 GET http://localhost:8080/persons
,您將看到類似於以下的內容
{ "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,並且還選取了存在的預設配置,以將參數解析為即將到來的請求的 Pageable
。這表示,如果您變更該配置,連結將自動遵循變更。預設情況下,組裝器指向調用它的控制器方法,但可以透過傳入自訂 Link
作為基礎來建構頁碼連結,以用於 PagedResourcesAssembler.toResource(…)
方法的重載。
2.7.2. Repository 填充器
如果您使用 Spring JDBC 模組,您可能熟悉使用 SQL 指令碼填充 DataSource
的支援。類似的抽象化在 repositories 層級可用,儘管它不使用 SQL 作為資料定義語言,因為它必須是與儲存無關的。因此,填充器支援 XML(透過 Spring 的 OXM 抽象化)和 JSON(透過 Jackson)來定義用於填充 repositories 的資料。
假設您有一個檔案 data.json
,其內容如下
[ { "_class" : "com.acme.Person",
"firstname" : "Dave",
"lastname" : "Matthews" },
{ "_class" : "com.acme.Person",
"firstname" : "Carter",
"lastname" : "Beauford" } ]
您可以透過使用 Spring Data Commons 中提供的 repository 命名空間的填充器元素輕鬆填充您的 repositories。若要將前面的資料填充到您的 PersonRepository,請執行以下操作
<?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:jackson-populator locations="classpath:data.json" />
</beans>
此宣告會導致讀取 data.json
檔案,並透過 Jackson ObjectMapper
將其還原序列化。
JSON 物件將要取消編組成的類型將透過檢查 JSON 文件的 _class
屬性來確定。基礎架構最終將選取適當的 repository 來處理剛還原序列化的物件。
若要改為使用 XML 來定義要使用其填充 repositories 的資料,您可以使用 unmarshaller-populator
元素。您將其配置為使用 Spring OXM 為您提供的 XML marshaller 選項之一。如需詳細資訊,請參閱 Spring 參考文件。
<?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>
2.7.3. 舊版 Web 支援
Spring MVC 的網域類別 Web 繫結
假設您正在開發 Spring MVC Web 應用程式,您通常必須從 URL 解析網域類別 ID。預設情況下,您的任務是將該請求參數或 URL 部分轉換為網域類別,然後將其交給下層,或直接在實體上執行業務邏輯。這看起來會像這樣
@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";
}
}
首先,您為每個控制器宣告一個 repository 依賴項,以查找由控制器或 repository 分別管理的實體。查找實體也是樣板程式碼,因為它始終是 findOne(…)
呼叫。幸運的是,Spring 提供了註冊自訂元件的方法,這些元件允許在 String
值與任意類型之間進行轉換。
PropertyEditors
對於 3.0 之前的 Spring 版本,必須使用簡單的 Java PropertyEditors
。為了與之整合,Spring Data 提供了 DomainClassPropertyEditorRegistrar
,它會查找在 ApplicationContext
中註冊的所有 Spring Data repositories,並為受管理的網域類別註冊自訂 PropertyEditor
。
<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>
如果您已如前面的範例中配置 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";
}
}
在 Spring 3.0 及更新版本中,PropertyEditor
的支援已被新的轉換基礎架構取代,此架構消除了 PropertyEditors
的缺點,並採用無狀態的 X 到 Y 轉換方法。Spring Data 現在隨附 DomainClassConverter
,它模仿了 DomainClassPropertyEditorRegistrar
的行為。若要進行設定,只需宣告一個 bean 實例,並將正在使用的 ConversionService
導入其建構子中。
<mvc:annotation-driven conversion-service="conversionService" />
<bean class="org.springframework.data.repository.support.DomainClassConverter">
<constructor-arg ref="conversionService" />
</bean>
如果您使用 JavaConfig,您可以簡單地擴展 Spring MVC 的 WebMvcConfigurationSupport
,並將組態超類別提供的 FormatingConversionService
傳遞到您建立的 DomainClassConverter
實例中。
class WebConfiguration extends WebMvcConfigurationSupport {
// Other configuration omitted
@Bean
public DomainClassConverter<?> domainClassConverter() {
return new DomainClassConverter<FormattingConversionService>(mvcConversionService());
}
}
Web 分頁
當在 Web 層處理分頁時,您通常必須自行編寫大量重複程式碼,才能從請求中提取必要的元數據。以下範例中顯示的較不理想的方法,需要方法包含一個必須手動解析的 HttpServletRequest
參數。此範例也省略了適當的錯誤處理,這會使程式碼更加冗長。
@Controller
@RequestMapping("/users")
public class UserController {
// DI code omitted
@RequestMapping
public String showUsers(Model model, HttpServletRequest request) {
int page = Integer.parseInt(request.getParameter("page"));
int pageSize = Integer.parseInt(request.getParameter("pageSize"));
Pageable pageable = new PageRequest(page, pageSize);
model.addAttribute("users", userService.getUsers(pageable));
return "users";
}
}
最重要的是,控制器不應處理從請求中提取分頁資訊的功能。因此,Spring Data 隨附 PageableHandlerMethodArgumentResolver
,它將為您完成這項工作。Spring MVC JavaConfig 支援公開了 WebMvcConfigurationSupport
輔助類別,以自訂組態,如下所示
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new PageableHandlerMethodArgumentResolver());
}
}
如果您仍在使用 XML 組態,您可以如下所示註冊解析器
<bean class="….web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="customArgumentResolvers">
<list>
<bean class="org.springframework.data.web.PageableHandlerMethodArgumentResolver" />
</list>
</property>
</bean>
一旦您使用 Spring MVC 設定了解析器,它就允許您將控制器簡化為如下所示
@Controller
@RequestMapping("/users")
public class UserController {
@RequestMapping
public String showUsers(Model model, Pageable pageable) {
model.addAttribute("users", userRepository.findAll(pageable));
return "users";
}
}
PageableArgumentResolver
會自動解析請求參數以建構 PageRequest
實例。預設情況下,它期望請求參數具有以下結構。
|
您想要檢索的頁面,從 0 開始索引,預設值為 0。 |
|
您想要檢索的頁面大小,預設值為 20。 |
|
排序指示的集合,格式為 |
若要檢索第三頁,最大頁面大小為 100,且資料依 email 屬性以升序排序,請使用以下 URL 參數
?page=2&size=100&sort=email,asc
若要依多個屬性以不同的排序順序排序資料,請使用以下 URL 參數
?sort=foo,asc&sort=bar,desc
如果您需要從請求中解析多個 Pageable
實例(例如,用於多個表格),您可以使用 Spring 的 @Qualifier
註釋來區分它們。然後,請求參數必須以 ${qualifier}_
作為前綴。因此,對於像這樣的函式簽名
public String showUsers(Model model,
@Qualifier("foo") Pageable first,
@Qualifier("bar") Pageable second) { … }
您必須填入 foo_page
和 bar_page
以及相關的子屬性。
在 bean 宣告上設定全域預設值後,PageableArgumentResolver
將預設使用第一頁和頁面大小為 10 的 PageRequest
。如果它無法從請求中解析 PageRequest
(例如,由於缺少參數),它將使用該值。您可以直接在 bean 宣告上設定全域預設值。如果您可能需要控制器方法特定的 Pageable
預設值,請使用 @PageableDefaults
註釋方法參數,並將頁面(透過 pageNumber
)、頁面大小(透過 value
)、sort
(要排序的屬性列表)和 sortDir
(排序方向)指定為註釋屬性
public String showUsers(Model model,
@PageableDefaults(pageNumber = 0, value = 30) Pageable pageable) { … }
3. 稽核
3.1. 基礎知識
Spring Data 提供了完善的支援,可以透明地追蹤誰建立或變更了實體,以及發生此事的時點。若要從該功能中受益,您必須為您的實體類別配備稽核元數據,可以使用註釋或實作介面來定義。
3.1.1. 基於註釋的稽核元數據
我們提供 @CreatedBy
、@LastModifiedBy
來捕獲建立或修改實體的用戶,以及 @CreatedDate
和 @LastModifiedDate
來捕獲發生此事的時點。
class Customer {
@CreatedBy
private User user;
@CreatedDate
private DateTime createdDate;
// … further properties omitted
}
如您所見,註釋可以選擇性地應用,具體取決於您要捕獲的資訊。對於捕獲時間點的註釋,可以用於 JodaTimes DateTime
、傳統 Java Date
和 Calendar
、JDK8 日期/時間類型以及 long
/Long
類型的屬性。
3.1.2. 基於介面的稽核元數據
如果您不想使用註釋來定義稽核元數據,您可以讓您的網域類別實作 Auditable
介面。它為所有稽核屬性公開了 setter 方法。
還有一個方便的基底類別 AbstractAuditable
,您可以擴展它以避免手動實作介面方法的需求。請注意,這會增加您的網域類別與 Spring Data 的耦合,這可能是您想要避免的。通常,基於註釋的定義稽核元數據的方式是首選,因為它侵入性較小且更靈活。
3.1.3. AuditorAware
如果您使用 @CreatedBy
或 @LastModifiedBy
,稽核基礎架構需要以某種方式感知到當前主體。為此,我們提供了一個 AuditorAware<T>
SPI 介面,您必須實作該介面以告知基礎架構誰是與應用程式互動的當前用戶或系統。泛型型別 T
定義了以 @CreatedBy
或 @LastModifiedBy
註釋的屬性必須是什麼類型。
這是使用 Spring Security 的 Authentication
物件實作介面的範例
class SpringSecurityAuditorAware implements AuditorAware<User> {
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return null;
}
return ((MyUserDetails) authentication.getPrincipal()).getUser();
}
}
此實作正在存取 Spring Security 提供的 Authentication
物件,並從中查找您在 UserDetailsService
實作中建立的自訂 UserDetails
實例。我們在這裡假設您正在透過該 UserDetails
實作公開網域用戶,但您也可以根據找到的 Authentication
從任何地方查找它。
附錄
附錄 A:命名空間參考
<repositories /> 元素
<repositories />
元素觸發 Spring Data 儲存庫基礎架構的設定。最重要的屬性是 base-package
,它定義要掃描 Spring Data 儲存庫介面的套件。[3]
名稱 | 描述 |
---|---|
|
定義要用於掃描儲存庫介面的套件,這些介面在自動偵測模式下擴展了 *Repository(實際介面由特定的 Spring Data 模組決定)。也會掃描配置套件下的所有套件。允許使用萬用字元。 |
|
定義後綴以自動偵測自訂儲存庫實作。名稱以配置後綴結尾的類別將被視為候選者。預設值為 |
|
確定用於建立查找器查詢的策略。有關詳細資訊,請參閱 查詢查找策略。預設值為 |
|
定義要查找包含外部定義查詢的 Properties 檔案的位置。 |
|
控制是否應考慮巢狀儲存庫介面定義。預設值為 |
附錄 B:Populators 命名空間參考
<populator /> 元素
<populator />
元素允許透過 Spring Data 儲存庫基礎架構填充資料儲存區。[4]
名稱 | 描述 |
---|---|
|
在哪裡找到要從儲存庫讀取物件的文件,儲存庫將使用這些物件填充。 |
附錄 C:儲存庫查詢關鍵字
支援的查詢關鍵字
下表列出了 Spring Data 儲存庫查詢衍生機制通常支援的關鍵字。但是,請查閱特定於儲存區的文件以獲取受支援關鍵字的確切列表,因為此處列出的一些關鍵字可能在特定儲存區中不受支援。
邏輯關鍵字 | 關鍵字表達式 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|