【翻譯】SpringData官方文檔第四章


title: 【翻譯】SpringData官方文檔第四章
date: 2016-11-27
tags:

  • 翻譯
    categories: 翻譯

說明:本翻譯4.64.7段發布在并發編程網,其他段落為了熟悉上下文而翻譯,沒有精校。首次參與翻譯任務,翻譯的不好請指正。


第四章 使用Spring Data Repositories

Spring Data repository abstraction目的是為了顯著的簡化必要的樣板式代碼來為多種持久化數據存儲實現數據層。

重要

本章解釋Spring Data repositories核心的概念和接口。Spring Data repository documentation 與 你的模塊。本章這些信息是從Spring Data Commons模塊獲取的。它使用了JPA模塊的配置和代碼示范,命名引用覆蓋XML配置,它支持所有的Spring Data模塊支持的repository API,Repository查詢關鍵字覆蓋被repository 接口一般的關鍵字查詢方法。你的模塊特性的細節信息,查詢文檔對應模塊。

4.1 核心概念

Spring Data repository抽象接口的核心是Repository(可能沒有那么驚喜)。它需要管理實體類以及實體類的id作為參數。此接口主要用作是獲取要使用的類型接口并幫助擴展這個接口。

CrudRepository為被管理的實體類提供復雜CRUD功能。

4.2 查詢方法

標準的CRUD功能repositories通常有查詢底層數據庫。

在Spring Data中,分詞聲明這些查詢變成了四個步驟的過程:

  1. 聲明一個接口繼承Repository或者它的一個子類,并且指定要被處理的實體類和Id類型。

    interface PersonRepository extends Repository<Person, Long> { … }
    
  1. 在接口中聲明查詢方法。

    interface PersonRepository extends Repository<Person, Long> {
      List<Person> findByLastname(String lastname);
    }
    
  1. 創建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并沒有明確配置一個包默認使用注解的包。

    為了定制包被掃描可以使用數據存儲注解@Enable的一個屬性basePackage

  1. 獲得repository實例注入并使用。

    public class SomeClient {
      @Autowired
      private PersonRepository repository;
      public void doSomething() {
        List<Person> persons = repository.findByLastname("Matthews");
      }
    }
    

以下部分詳細說明每個步驟。

4.3 定義repository接口

第一步定義一個實體類依賴repository接口。

這個接口必須繼承Repository接口并且指定實體類型和Id類型。

如果你希望實體類型擁有CRUD方法,將Repository接口替換成繼承CrudRepository

4.3.1 小巧repository定義

一般情況下,你的repository接口應該繼承RepositoryCrudRepository或者PagingAndSortingRepository

除此之外,如果你不想繼承Spring Data接口,你也可以使用@Repository注解定義你的接口

繼承CrudRepository提供了一系列完整的方法來操縱你的實體.

如果你希望選擇方法,簡單的從CurdRepository復制你希望獲得的方法到你的Repository

注意

注意這允許你定義你自己的抽閑建立在Spring Data Repositories功能.

例5.有選擇的展現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);
}

這是第一步你定義一個通用的基本接口,接口供你所有的實體repositories使用并提供findOne()save()方法

這些方法會被轉發到你選擇的Spring Data提供的基本repository實現,例如JPASimpleJpaRepository,因為他們匹配CrudRepository的方法簽名.

因此UserPepository現在可以保存用戶,查找唯一用戶根據id,并且觸發一個查詢去查找Users根據他們的郵箱地址

注意

注意,中間的repository接口使用了注解NoRepositoryBean.

對所有Spring Data在運行時不需要生成實例的repository接口,確保你為他們添加了注解.

4.3.2 在多個Spring Data模塊使用Repositories

在你的應用中使用唯一的Spring Data模塊,所有repository接口定義范圍限制在Spring Data模塊.

有時候應用需要使用不止一個Spring Data模塊.

這種情況下,需要repository定義在持久化技術之間有所區別.

在class path發現多個repository工廠時,Spring Data嚴格檢測repository配置模塊.

在repository或者實體類嚴格配置需要得細節以決定Spring Data模塊綁定一個repository的定義:

  1. 如果repository定義繼承模塊特殊的repository,那么對Spring Data模塊這是一個有效的備選.
  1. 如果實體類被模塊特有的注解類型注解,那么對Spring Data模塊這是一個有效的備選.

    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> {
  …
}

MyRepositoryUserRepository繼承JpaRepository在他們類型層級.他們有效的表示了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> {
  …
}

AmbiguousRepositoryAmbiguousUserRepository在它們繼承體系里只繼承了RepositoryCrudRepository.使用唯一的Spring Data模塊是這是完成正確的,多個模塊不能識別Repository到底綁定哪個Spring Data.

例8.使用注解配合實體類定義Repositor

interface PersonRepository extends Repository<Person, Long> {
  …
}

@Entity
public class Person {
  …
}

interface UserRepository extends Repository<User, Long> {
  …
}

@Document
public class User {
  …
}

PersonRepository引用使用了JPA的注解@Entity進行注解的Person,所以這個repository明確的屬于Spring Data JPA.UserRepository使用了Spring Data MongoDB的注解@Document進行注解.

例9.使用混合注解配合實體類定義Repositor

interface JpaPersonRepository extends Repository<Person, Long> {
  …
}

interface MongoDBPersonRepository extends Repository<Person, Long> {
  …
}

@Entity
@Document
public class Person {
  …
}

這個示例展示實體類同時使用JPA和Spring Data MongoDB注解.

這里定義了兩個repository,JpaPersonRepositoryMongoDBPersonRepository.

一個被JPA使用,另一個被MongoDB使用.

Spring Data不再能告訴repository區分,這將導致不清晰的行為

"使用模塊特有的接口定義Repository"和"使用注解配合實體類定義Repositor"都使用了嚴格的repository配置為一個特定的Spring Data模塊識別可選的repository

使用多種持久化技術特定的注解在同一個實體類上可能在鍋中持久化技術上重用了實體類,但是這樣做Spring Data將不能確定為repository綁定唯一的模塊

最后一種方式區分repository是劃分repository的基本包.

基本包定義起始點是掃描repository接口定義,這意味著在合適的包定義repository.

默認的,注解配置使用類配置的包

基于XML基本配置在這里.

例10.注解配置基本包

@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }

4.4 Defining query methods

4.4 定義查詢方法

repository代理有兩種方式獲得通過方法名獲得指定查詢.可以通過方法名直接獲得查詢,或者手動定義查詢.有可用的選項定義在實際的存儲.這有一些策略決定實際查詢如何被創建.讓我們一些看看可用的選項.

4.4.1 Query lookup strategies

下面這些repository基本組件的決定查詢可用的策略.你可以配置策略,在XML配置中通過命名空間query-look-strategy屬性或者在Java配置中通過啟用${store} Repository的屬性注解queryLookupStrategy.一些策略可能不能支持特定的數據庫.

  • CREATE 試圖通過方法名構建一個指定查詢.一般處理是從方法名移除一系列給定的前綴并解析方法其他部分.更多查詢構建信息閱讀Query creation.
  • USE_DECLARED_QUERY試圖查找一個準確的查詢,并在找不到時拋出一個異常.查詢可以被通過注解定義或者其它方式定義.查詢特殊存儲的文檔了解存儲的可用選擇.如果repository基本組件在啟動時不能為方法找到一個準確的查詢,將會失敗
  • CREATE_IF_NOT_FOUND(默認)聯合了CREATE和USE_DECLARED_QUERY.首先查找一個準確查詢,如果沒有找到,創建一個定制方法基于名字的查詢.這是默認的查詢策略,因此如果你沒有做任何明確配置.它允許根據方法名快速查詢定義,而且定制查詢根據需要引入聲明的查詢.

4.4.2 查詢創建

建成Spring Data repository基本組件的查詢構建機制有用的構建了repository所有實體類的約束查詢.

這個機制分隔方法前綴find...By,read...By,query...By,count...By以及get...By,并解析其他部分.這個引入條款可以表達包含的特性例如Distinct,來設置明確的標志在要生成的查詢上.然而,第一個by扮演了分解符來指明真實條件的起始.分詞在一個非常基本的水平,你可以在實體屬性上定義條件并且連接使用and或or連接他們.

例11.來自方法名的查詢創建

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,granterThan,like.你可以使用的表達式操作符有between,LessThan,GreaterThan,Loke.被支持的操作符根據多樣的數據庫決定,因此查詢你引用文檔的恰當部分.
  • 方法解析支持為單獨屬性設置一個IgnoreCase標志(例如,findByLastnameIgnoreCase(...))或者為所有屬性都支持忽略情況(通常是String情形,例如,findByLastnameAndFirstnameAllIgnoreCase(…)).忽略情況是否被支持由多樣的數據庫決定,所以具體存儲查詢方法在引用文檔查詢相關章節.
  • 你可以為查詢方法引用的屬性提供靜態排序連接OrderBy子句并且提供排序方向(Asc或Desc).為創建一個查詢方法支持動態排序,查看特殊參數處理.

4.4.3 屬性表達式

屬性表達式可以只為管理的實體類的直接屬性使用,就像前面所展示的那樣。查詢常見時你已經確認解析的字段是管理的實體類的一個字段.然而,你也可以通過最近的字段定義一個約束.假設PersonAddress有一個ZipCode字段.這種情況一個方法如果這樣命名:

List<Person> findByAddressZipCode(ZipCode zipCode);

創建屬性遍歷x.address.zipCode.決策算法從把全部(AddressZipCode)作為屬性開始并且檢查實體類依此命名的屬性(小寫開頭).如果算法成功了,就是用這個屬性.否則,算法將源碼部分駝峰式大小寫從右側頭部到尾巴分隔,并試圖找到相應的屬性,在我們的例子中,AddressZipCode.如果從頭部找到一個屬性,算法將在這里生成樹處理后面的部分,使用描述的方式分隔.如果第一個分隔沒有匹配到,算法移動分割點到左側繼續(Address,ZipCode).

這在大多數情況都可以使用,但也可能選擇錯誤的屬性.假設Person類也有一個addressZip屬性.算法將匹配第一個匹配的,本質上選擇了錯誤屬性,最終失敗(伴隨的addressZip類型沒有屬性code).

沒了解決這種起義,你可以在方法名稱內使用_手動的定義遍歷點.所以我們的方法名稱最終像這樣:

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)

我們對待下劃線當做一個保留關鍵字,我們強力建議遵循標準Java命名規范(例如,使用駝峰命名而不是下劃線命名屬性名稱)

4.4.4 特殊參數處理

處理你查詢中的參數,你可以定義簡單的方法參數像上面的示例中.處理之外基本組件可以識別出某些特殊類型例如PageableSort用來動態的編碼和排序你的查詢.

例12.在查詢方法使用Pageable,SliceSort

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代替.一個Slict只知道下一個Slice可到到哪里,當運行在一個大的結果集上時這可能已經足夠了.排序選項也通過Pageable實例處理.如果你只需要排序,簡單起見添加org.springframework.data.domain.Sort參數在你的方法.如你所見,簡單返回一個列表.在這種情況下,額外元數據構建實際的Page實例將不會被創建(反過來這意味著,沒有發出額外的統計查詢所必須的)簡單約束只在給定的范圍內查詢.

注意

為了盡早得到你查詢了多少頁,你必須出發一個格外統計查詢.默認的,這個查詢你實際觸發的查詢調用.

4.4.5 限制查詢結果

查詢方法的結果可以通過關鍵first或者top限制,可以交換使用.一個可選的數值可以追加在top/first來指定返回的最大結果集.如果數字缺失,假定結果集大小是1.

例13.查詢中使用Top和First限制結果大小

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);

限制表達式也支持Distinct關鍵字.此外,對于將結果集設置為一個實例的查詢,支持將結果包裝到一個Optional.如果應用分頁或分片限制查詢分頁(并且計算可用的頁數)那么這可以應用limited結果.

注意

請注意,限制結果結合使用Sort的動態排序的結果允許參數可以表示“k”最小的查詢方法以及“K”的查詢方法最大的元素。

4.4.6 流式處理結果

方法查詢結果可以通過使用java 8Stream<T>逐步處理。

特定的方法用來表示流而不是簡單的包裝查詢結果在一個Stream數據存儲

例14 一個用java流8Stream<T>查詢結果

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

注意

一個Stream潛在的包裝底層數據存儲在頁數的資源中,因此使用完畢必須關閉.你可以使用close()方法手動關閉Stream或者使用一個Java7的try_with-resources塊.

try(Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
  stream.forEach(...);
}

4.4.7 異步查詢結果

Repository查詢可以使用Spring's asynchronous method execution capacity執行異步.這意味著方法可以一執行立即返回,真實的查詢執行發生在任務提交到一個Spring TaskExecutor.

@Async
Future<User> findByFirstname(String firstname);

@Async
CompletableFuture<User> findOneByFirstname(String firstname);

@Async
ListenableFuture<User> findOneByLastname(String lastname);
  1. 使用java.util.concurrent.Future作為返回類型
  2. 使用一個Java8java.util.current.CompletableFuture作為返回類型
  3. 使用一個org.springframework.util.concurrent.ListenableFuture作為返回類型

創建repository實例

這個章節你創建實例和bean定義為repository接口定義.方法之一是手動的支持repository使用包含各個Spring Data模塊的Spring命名空間裝載,然而我們一般推薦使用Java配置的方法配置.

4.5.1 XML配置

每個Spring Data module包含一個repository 元素,這可以讓你簡單的定義一個基本包,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>

在前面的實例中,讓Spring掃描com.acme.repositories包和它的子包里繼承Repository的接口或者它的子接口.找到的每個接口,繼承組件注冊持久化技術的FactoryBean來創建合適的代理處理執行查詢方法.每個bean被注冊在一個接口名稱確定的bean name下,所以一個叫UserRepository將注冊userRepository.基本包屬性允許通配符,所以你可以定義一個規則掃描包.

使用過濾

默認的基本組件選取所有在配置的基本包下繼承了持久化技術Repositpry接口以及子接口

并且為他們創建一個bean實例.然而,你可能希望更細粒度的控制哪個接口bean實例被創建.為了實現這個你可以在<repository/>中使用<include-filter/><exclude-filter/>元素.語義完全等同于Spring的上下文命名空間中的元素.更多細節,查看他們的元素Spring reference documentation

例如,要將某些確定的接口排除實例化為repository,可以使用以下配置:

例17. 使用exclude-filter元素

<repository base-package="com.acme.repositories">
  <context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>

這個示例從待實例化對象中排除所有以SomeRepository結尾的接口.

4.5.2 JavaConfig

repository基礎組件也可以使用一個存儲的特殊的@Enable${store}Repositories注解在一個JavaConfig類上.入門介紹Spring容器Java基本Spring容器查看文檔:JavaConfig in the Spring reference documentaional

一個簡單配置啟用Spring Data repositories像這樣:

例18. repository配置基于簡單注解

@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
  @Bean
  public EntityManagerFactory entityManagerFactory() {
    // …
  }
}

注意

示例使用了JPA特有的注解,根據你使用的存儲模塊決定實際如何替換.示例定義EntityManagerFactorybean.查閱具體存儲的配置

4.5.3 單獨使用

你也可以在Spring容器之外使用repository基礎組件,例如在CDI環境.你仍然需要一些Spring庫在你的classpath中,但是你可以以編程的方式啟動.repository包裝持久化技術支持的Spring Data模塊提供RepositoryFactory,你可以向下面這樣使用:

例19. repository工廠單獨使用

RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);

4.6 定制Spring Data倉庫實現

時常有必要為一少部分倉庫方法提供一個定制的實現.Spring數據存儲庫很容易允許您提供自定義存儲庫代碼并將其與通用CRUD集成抽象和查詢方法功能整合.

4.6.1 為單獨倉庫添加定制行為

為了定制功能豐富一個倉庫,你首先為定制功能定義一個接口和實現.使用你提供的倉庫接口來繼承自定義接口.

例20. 定制倉庫功能的接口

interface UserRepositoryCustom {
  public void someCustomMethod(User user);
}

例21.定制功能的實現

class UserRepositoryImpl implements UserRepositoryCustom {
  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}

注意

類可以被找到最重要的點是名字以Impl為后綴區別于倉庫的核心接口(見下文)

實現的本身沒有依賴Spring Data,可以是一個標準的Spring bean.所以你可以使用標準的依賴注入行為給其他bean注入引用,像JdbcTemplate,切面的一部分等等.

例22 修改你基本的倉庫接口

interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {
  // Declare query methods here
}

讓你的標準倉庫接口繼承定制的.這樣做結合了CRUD和定制功能并使其可用于客戶端.

配置

如果你使用命名空間配置,倉庫基本組件掃描類所在包,根據掃描結果嘗試自動定制實現.

這些類需要遵循命名規范:給倉庫接口名添加命名空間元素屬性repositoryimpl-postfix.默認的后綴是Impl

例23. 配置示例

<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="FooBar"/>

第一個配置示例將查實查找一個類com.acme.repository.UserRepositoryImpl來作為定制藏局實現.然而第二個示例將嘗試查找com.acme.repository.UserRepositoryFooBar.

手動指定

上面的方法可以正常工作,只有當你定制實現使用注解配置和自動注入,這將與其他Spring bean一樣被對待.如果你定制的實現需要特殊處理,你可以像描述的簡單定義一個bean并且命名它.基本組件將通過名稱引用手動定義的bean定義而不是它自己創建一個.

例24.手動指定定制實現

<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
  <!-- further configuration -->
</beans:bean>

4.6.2為所有倉庫添加定制行為

當你希望把一個單獨的方法添加到你所有的倉庫接口中時,上面的方法就不可行了.為了添加定制到所有的倉庫,你首先添加一個中間接口來定義共享的行為.

例25 定義共享定制行為接口

@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable>
  extends PagingAndSortingRepository<T, ID> {
  void sharedCustomMethod(ID id);
}

現在你獨立的倉庫接口將繼承這個中間接口而不是Repository接口來包含功能的定義.接下來創建一個中間接口的實現繼承持久化具體倉庫的基本類.這個類后面將作為倉庫代理的基本類.

例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
  }
}

警告

這個類需要有一個構造方法調用父類具體存儲倉庫工廠實現.萬一倉庫基礎類有多個構造,覆蓋包括一個EntityInformation加上存儲具體基本組件對象(例如一個EntityManager或者模板類)

Spring<repository/>命名空間下的默認行為為所有接口提供一個實現.這意味著如果處于其當前狀態,MyRepository的實現實例將由Spring創建.這當然不是被期望的,它只是作為一個用來定義實體的 Repository和真實倉庫接口的中間接口.為了排除一個繼承Repository的接口被當做一個倉庫接口實例化,你可以給它使用@NoRepositoryBean(像上面)或者把它從配置中base-package移除.

最后一步是讓Spring Data基本組件識別到定制的倉庫基本類.在JavaConf使用注解@Enable...Repository的屬性repositoryBaseClass完成:

例27 使用JavaConfig配置一個定制倉庫基本類

@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }

類似的屬性在XML命名空間中也可以找到.

例28 使用XML配置一個定制倉庫基本類

<repositories base-package="com.acme.repository"
              base-class="….MyRepositoryImpl" />

4.7 Spring Data擴展

這部分說明Spring Data一系列的擴展功能,可以使Spring Dta使用多樣的上下文.目前大部分集成是針對Spring MVC.

4.7.1 Querydsl擴展

Querydsl是一個框架,通過它的流式API構建靜態類型的SQL類查詢.

多個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.
}

① 查詢并返回一個匹配Predicate的單例實體

②查詢并返回所有匹配Predicate的實體

③ 返回匹配Predicate的實體數量

④ 返回是否存在一個匹配Predicate的實體

為了簡單的使用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支持

注意

本節包含Spring Data Web支持的文檔是在1.6范圍內的Spring Data Commons實現的.因為支持新引入的內容改變了很多東西,我們保留了舊行為的文檔在"遺留Web支持"部分.

如果模塊支持倉庫編程模型,那么Spring Data模塊附帶了各種Web模塊支持.Web關聯的東西需要Spring MVC的JAR包位于classpath路徑下,它們中有些甚至提供了Spring HATEOAS集成.一般情況,集成方式支持使用@EnableSpringDataWebSupport注解在你的JavaConfig配置類.

例31 啟用Spring Data web支持

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}

@EnableSpringDataWebSupport注解注冊了一些組件,我們將在稍后討論.注解還將在類路徑上檢測Spring HATEOAS,如果才在將為其注冊集成組件.

作為可選項,如果你使用XML配置,注冊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" />

基本Web支持

上面展示的的配置設置將注冊幾個基本組件:

  • 一個DomainClassConverter啟用Spring MVC來根據請求參數或路徑變量管理倉例實體類的實例
  • HandlerMethodArgumentResolver實現讓Spring MVC從請求參數解析Pageable和Sort實例.

實體類轉換

DomainClassConverter允許你在Spring MVC控制器方法簽名中直接使用實體類型,因此你不必手動的通過倉庫查詢實例:

例33 一個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";
  }
}

如你所見,方法直接接收一個User實例并沒有更進一步的查詢是否必要.實例可以通過Spring MVC將路徑變量轉換為實體類的id類型并最終通過在實體類型注冊的倉庫實例上調用findOne(...)訪問實例轉換得到.

注意

當前的倉庫必須實現CrudRepository做好準備被發現來進行轉換.

為了分頁和排序分解方法參數

上面的配置片段還注冊了一個PageableHandlerMethodArgumentResolver和一個SortHandlerMethodArgumentResolver實例.注冊使得Pageable和Sort成為有效的控制器方法參數.

例34 使用Pageable作為控制器方法參數

@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實例:

表1 請求參數轉換Pageable實例

參數 說明
page 要檢索的頁面,索引為0,默認為0
size 要檢索的頁面大小,默認20
sort 被排序的屬性應以格式property,property(, ASC|DESC)表示.默認生序排序,如果你希望改變排序順序,則使用多個排序參數,例如?sort=firstname&sort=lastname,asc

為了定制行為,可以繼承SpringDataWebConfiguration或者啟用等效的HATEOAS并覆蓋pageableResolver()sortResolver()方法并導入你的自定義配置文件替代@Enable-注解.

有一種情況你需要多個PageableSort實例從請求轉換(例如處理多個表單),你可以使用Spring的@Qualifier注解來互相區別.請求參數必須以${qualifier}為前綴.這樣一個方法的簽名像這樣:

public String showUsers(Model model, 
                        @Qualifier("foo")Pagebale first, 
                        @Qualifier("bar") Pageable second) {
  ...
}

你必須填充foo_page和bar_page等.

默認的Pageable在方法中處理等價于一個new PageRequest(0, 20),但是可以使用@PageableDefaults注解在Pageable參數上定制.

Hypermedia支持分頁

Spring HATEOAS包裝了一個代表模型的類PageResources ,

它可以使用Page實例包裝必要的Page元數據內容作為連接讓客戶端導航頁面.一個頁面到一個PageResources的轉換被Spring HATEOAS的ResourceAssembler接口實現PagedResourcesAssembler來完成.

例35 使用一個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);
  }
}

像上面這樣配置將允許PageResourcesAssembler作為控制器方法的一個參數.在這調用toResources(...)方法有以下作用:

  • Page的內容將PageResources實例的內容
  • PageResources將獲得PageMetadata實例,該實例由Page和基礎的PageRequest中的信息填充
  • PageResources獲得prevnext連接,添加這些依賴在頁面.這些鏈接將指向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(...)方法定制.

Querydsl web 支持

那些整合了QueryDSL的存儲可能從Request查詢字符串中的屬性驅動查詢.

這意味著前面例子的查詢字符串可以給出User的對象

?firstname=Dave&lastname=Matthews

可以被轉換為

QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))

使用了QuerydslPredicateArgumentResolver.

注意

當在類路徑上找到Querydsl時,該功能將在@EnableSpringDataWebSupport注解中自動啟用

添加一個@QuerydslPredicate到一個方法簽名將提供一個就緒的Predicate,可以通過QueryDslPredicateExecutor執行.

提示

類型信息通常從返回方法上解析.由于這些信息不一定匹配實體類型,使用QuerydslPredicateroot屬性可能是個好主意.

@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轉換匹配查詢字符串參數的Predicate

默認的綁定規則如下:

  1. Object在簡單屬性上如同eq

  2. Object在集合作為屬性如同contains

  3. Collection在簡單屬性上如同in

這些綁定可以通過@QuerydslPredicatebindings屬性定制或者使用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);
    }
  }
  1. QueryDslPredicateExecutorPredicate提供特殊的查詢方法提供入口
  2. 在倉庫接口定義QuerydslBinderCustomizer將自動注解@QuerydslPredicate(bindings=...)
  3. username屬性定義綁定,綁定到一個簡單集合
  4. String屬性定義默認綁定到一個不區分大小寫的集合
  5. Predicate移除密碼屬性

4.7.3 倉庫填充

如果你使用Spring JDBC模塊,你可能熟悉在DataSource使用SQL腳本來填充.一個類似的抽象在倉庫級別可以使用,盡管它不是使用SQL作為數據定義語言,因為它必須由存儲決定.填充根據倉庫支持XML(通過Spring的OXM抽象)和JSON(通過Jackson)定義數據.

假設你有一個文件data.json內容如下:

例36 JSON定義的數據

[ { "_class" : "com.acme.Person",
     "firstname" : "Dave",
      "lastname" : "Matthews" },
      { "_class" : "com.acme.Person",
     "firstname" : "Carter",
      "lastname" : "Beauford" } ]

你可以容易的根據Spring Data Commons提供倉庫的命名空間填充元素填充你的倉庫.為了填充前面的數據到你的PersonRepository,像下面這樣配置:

例37 聲明一個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文件可以被一個Jackson的ObjectMpper讀取和反序列化.

JSON將要解析的對象類型由檢查JSON文檔的_class屬性決定.基本組件將最終選擇合適的倉庫去處理反序列化的對象.

要使用XML定義數據填充倉庫,你可以使用unmarshaller-populator元素.你配置它使用Spring OXM提供給你的XML裝配選項.在Spring reference documentation查看更多細節.

例38 聲明一個裝配倉庫填充器(使用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的實體類綁定

如果正在開發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";
  }
}

首先你為每個控制器定義一個依賴的倉庫來查找它們分別管理的實體.查詢實體也是樣板,因為它總是一個findOne(...)調用.幸運的Spring提供了方法來注冊自定義組件,允許一個String值轉換到一個屬性類型.

屬性編輯

Spring3.0之前JavaPropertyEditors被使用.為了集成這些,Spring Data提出一個DomainClassPropertyEditorRegistrar來查詢所有注冊到ApplicatonContext的Spring Data倉庫和一個定制的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";
  }
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容