Spring Data

核心概念

Repository

Repository 是Spring Data 的核心接口 。 它將domain 類 和 domain 類的ID作為管理參數(shù) 。 其他的Repository都繼承實(shí)現(xiàn)該接口。 如CrudRepository :

public interface CrudRepository<T, ID extends Serializable>
  extends Repository<T, ID> {

  <S extends T> S save(S entity);      

  Optional<T> findById(ID primaryKey); 

  Iterable<T> findAll();               

  long count();                        

  void delete(T entity);               

  boolean existsById(ID primaryKey);   

  // … more functionality omitted.
}

其他特定技術(shù)的抽象如 JpaRepository or MongoRepository 都是CrudRepository的子類。

在CrudRepository上層還有一個(gè)抽象接口 PagingAndSortingRepository , 提供分頁排序功能:

public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

Query 方法

使用spring data 進(jìn)行數(shù)據(jù)查詢有如下四個(gè)步驟:

  1. 聲明一個(gè)接口, 該接口需要繼承Repository 或其子接口 ,并提供domain 類和id參數(shù), 如下:
interface PersonRepository extends Repository<Person, Long> { … }
  1. 在該接口中聲明查詢方法:
interface PersonRepository extends Repository<Person, Long> {
  List<Person> findByLastname(String lastname);
}
  1. 使用spring建立該接口的代理接口, 可以通過JavaConfig 或 Xml 配置。
    3.1 使用Javaconfig
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@EnableJpaRepositories
class Config {}

3.2 使用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名稱空間。若使用其他的數(shù)據(jù)源,需要修改為其他的名稱空間 。

注意:javaConfig 并咩有顯示聲明包名稱, 因?yàn)槟J(rèn)會(huì)使用注解所在的包。 要修改默認(rèn)需要使用 basePackage屬性。

  1. 注入repository實(shí)例并使用之 :
class SomeClient {

  private final PersonRepository repository;

  SomeClient(PersonRepository repository) {
    this.repository = repository;
  }

  void doSomething() {
    List<Person> persons = repository.findByLastname("Matthews");
  }
}

后續(xù)章節(jié)將詳細(xì)介紹這4個(gè)步驟。

定義Repository 接口

首先,定義repository接口,需要指定domain class 和id 類型 , 可以直接繼承Repository接口,或者其子接口如CrudRepository 。

調(diào)整Repository接口

一般的,定義repository接口需要繼承 Repository, CrudRepository, or PagingAndSortingRepository. 但如果不想使用Spring data提供的接口,也可以自定義 。自定義接口上加 @RepositoryDefinition 注解。

當(dāng)繼承CrudRepository時(shí), 會(huì)暴露所有的CRUD接口, 若不想暴露那么多 , 則可以直接從Repository繼承, 如下所示:

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {

  Optional<T> findById(ID id);

  <S extends T> S save(S entity);
}

interface UserRepository extends MyBaseRepository<User, Long> {
  User findByEmailAddress(EmailAddress emailAddress);
}

上例中, UserRepository 只會(huì)暴露 findById 、save findByEmailAddress接口, 不會(huì)暴露其他的 。

注意: @NoRepositoryBean 使spring data 不會(huì)生成相應(yīng)repository的實(shí)例, 用在中間repository定義上。

Repository 的NULL處理

從spring data 2.0 開始 , repository的方法使用java 8 的Optional來表示可能缺少的值。 除此之外,spring data還為查詢提供如下的封裝類:

  • com.google.common.base.Optional

  • scala.Option

  • io.vavr.control.Option

  • javaslang.control.Option (deprecated as Javaslang is deprecated)

另外, 查詢方法可以選擇不使用這些封裝類。 通過返回null表示沒有查詢結(jié)果 。 Repository 方法返回集合、變種集合、封裝、流 時(shí)會(huì)保證不會(huì)出現(xiàn)null , 而是返回相應(yīng)的空。

repository 方法的可空注解如下:

  • @NonNullApi : 在包級(jí)別上使用,以聲明參數(shù)和返回值的默認(rèn)行為是不接受或生成空值。
  • @NonNull : 使用在參數(shù)或返回值上,他們不能為null (在使用了@NonNullApi時(shí)不需要。)
  • @Nullable : 使用在參數(shù)或返回值上, 表示可以為null。

如:在package-info.java 上聲明:

@org.springframework.lang.NonNullApi
package com.acme;

如例:

package com.acme;                                                       
//  該包定義在我們聲明的non-null包中;  

import org.springframework.lang.Nullable;

interface UserRepository extends Repository<User, Long> {

  // 當(dāng)返回為空時(shí)拋出 EmptyResultDataAccessException  異常 。 當(dāng)輸入?yún)?shù)emailAddress空時(shí)拋出 IllegalArgumentException 
  User getByEmailAddress(EmailAddress emailAddress);                    

  @Nullable
//  允許輸入?yún)?shù)為空; 允許返回空值 
  User findByEmailAddress(@Nullable EmailAddress emailAdress);          

// 當(dāng)沒有查詢結(jié)果時(shí)返回Optional.empty() ; 當(dāng)輸入emailAddress為空時(shí)拋出IllegalArgumentException 
  Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress); 
}

多數(shù)據(jù)源repository

當(dāng)使用單一spring data module時(shí),會(huì)很簡單, 因?yàn)樗械膔epository都會(huì)綁定到該模塊上。 但是當(dāng)應(yīng)用要求多個(gè)數(shù)據(jù)module時(shí), 需要明確repository使用的模塊 。

當(dāng)spring 檢查到引入多個(gè)數(shù)據(jù)module時(shí), 他會(huì)按照如下規(guī)則進(jìn)行判斷:

  1. repository 是否繼承自特定數(shù)據(jù)源, 將根據(jù)特定數(shù)據(jù)源進(jìn)行判斷。
  2. domain 類上是否有特定數(shù)據(jù)源的注解, spring data支持第三方的注解(如JPA的@Entity ) , 也有自己的注解 (如Mongo和Elasticsearch的@Document )

如下例子顯示了使用JPA的例子:

interface MyRepository extends JpaRepository<User, Long> { }

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
  …
}

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

MyRepository and UserRepository 繼承自JpaRepository , 會(huì)使用spring data JPA。

如下例子 repository 繼承通用的repository:

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

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
  …
}

interface AmbiguousUserRepository extends MyBaseRepository<User, Long> {
  …
}

通過上述繼承關(guān)系是無法判斷的 。
在定義user類時(shí), 若使用@Entity ,則使用的是 JPA ; 若使用的是@Document , 則使用的是mongo。

如下例子在person上同時(shí)使用了兩個(gè)注解, 會(huì)引發(fā)異常:

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

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

@Entity
@Document
class Person {
  …
}

在同一domain類型上使用多個(gè)持久性技術(shù)特定的注釋是可能的,并允許跨多種持久性技術(shù)重用域類型。 但是,Spring Data不再能夠確定用于綁定存儲(chǔ)庫的唯一模塊。

區(qū)分repository的最后一種方法是定義使用repository的范圍。 基礎(chǔ)包定義了掃描repository接口定義的起點(diǎn),這意味著將repository定義放在相應(yīng)的包中。 默認(rèn)情況下,注釋驅(qū)動(dòng)的配置使用配置類的包。 基于XML的配置中的基本包是必需的。

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

定義查詢方法

有兩種方式來確定查詢方法:

  • 根據(jù)方法名推斷
  • 手工定義查詢

查詢策略

使用XML配置,您可以通過query-lookup-strategy屬性在命名空間配置策略。 對(duì)于Java配置,您可以使用Enable $ {store}存儲(chǔ)庫注釋的queryLookupStrategy屬性。 特定數(shù)據(jù)存儲(chǔ)可能不支持某些策略。

  • CREATE: 從方法名構(gòu)造特定數(shù)據(jù)源的查詢語句 。 一般方法是去掉方法前綴,解析其余部分。后面有詳細(xì)介紹。
  • USE_DECLARED_QUERY : 查找聲明的查詢,未找到則會(huì)拋出異常 。 可以是注解或其他方式。 repository在啟動(dòng)時(shí)嘗試查找相應(yīng)數(shù)據(jù)源的聲明, 未找到則fail。
  • CREATE_IF_NOT_FOUND : 默認(rèn)選項(xiàng)。 結(jié)合 CREATE and USE_DECLARED_QUERY 。 它首先查找聲明,未聲明則創(chuàng)建一個(gè)基于方法名的查詢 。 這是默認(rèn)方式。 它允許根據(jù)方法名快速定義,也允許自定義查詢 。

Query Creation

其機(jī)制是剝離前綴find ... By,read ... By,query ... By,count ... By,and get ...By ,然后解析其余部分。同時(shí)也可以使用And Or distinct等等。

例:

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ù)據(jù)源有所不同, 但是有些共同的部分:

  • 表達(dá)式是屬性和運(yùn)算符的結(jié)合, 支持AND 、 OR 、Between 、LessThen 、GreaterThen 、like 等, 另外因數(shù)據(jù)源不同有些操作符也不同。
  • 方法解析器支持為各個(gè)屬性設(shè)置IgnoreCase標(biāo)志(例如,findByLastnameIgnoreCase(...))或支持所有屬性忽略大小寫(通常是String實(shí)例 - 例如,findByLastnameAndFirstnameAllIgnoreCase(...))。 是否支持忽略大小寫可能因數(shù)據(jù)源不同而異,因此請(qǐng)參閱參考文檔中有關(guān)查詢方法的相關(guān)章節(jié)。
  • 排序, 使用OrderBy后接字段來實(shí)現(xiàn), 并可以指定方向(Asc或Desc)

屬性表達(dá)式

如 : person中定義了Address , address 有zipCode字段:
List<Person> findByAddressZipCode(ZipCode zipCode);

spring data 會(huì)嘗試去判斷在哪兒進(jìn)行分割 ,有可能會(huì)出現(xiàn)錯(cuò)誤。

建議使用進(jìn)行顯示分割,以防其分割錯(cuò)誤:
List<Person> findByAddress_ZipCode(ZipCode zipCode);
但是,
在java中是保留字符, 不建議使用, 建議使用駝峰結(jié)構(gòu)來表示。

特殊參數(shù)處理

特定參數(shù)指分頁 排序, Pageable and Sort 。 這倆參數(shù)將被特殊對(duì)待。
如下:

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è)方法傳入?yún)?shù) org.springframework.data.domain.Pageable 以生成動(dòng)態(tài)分頁查詢。 返回的Page對(duì)象有全部頁數(shù)信息和當(dāng)前頁信息。 全部頁數(shù)信息是通過一個(gè)計(jì)數(shù)語句完成的。這在某些數(shù)據(jù)源上可能會(huì)比較昂貴。 另一種方式是返回Slice結(jié)構(gòu), 該結(jié)構(gòu)會(huì)包含一個(gè)是否還有下一頁數(shù)據(jù)字段,這對(duì)大數(shù)據(jù)集非常有用。

Pageable 實(shí)例同時(shí)會(huì)處理sort 。 若只需要sort ,可以直接使用 org.springframework.data.domain.Sort 。 分頁也可以只返回List, 這樣不會(huì)觸發(fā)count查詢, 但是這只適用于查詢給定范圍的信息。

限制查詢結(jié)果

查詢結(jié)果可以通過附加first 和top 關(guān)鍵字來返回一部分?jǐn)?shù)據(jù)。 first 和top后跟一個(gè)數(shù)字, 若沒有數(shù)字, 則默認(rèn)為1 。
例子:

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 關(guān)鍵字。 查詢結(jié)果也可以包裝為Optional。

若限制查詢中使用了 page 或slice , 則是對(duì)限制查詢后的結(jié)果進(jìn)行page 或slice 。

流化查詢結(jié)果

可以將查詢結(jié)果返回為Java8 Stream<T> 結(jié)構(gòu), 或者使用特定數(shù)據(jù)源的stream查詢。
例子:

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

Stream<User> readAllByFirstnameNotNull();

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

使用stream 需要在使用完后進(jìn)行close , 可以手工close , 或使用try-with-resources結(jié)構(gòu):

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

注意: 不是所有的spring data模塊都支持stream

異步查詢

某些數(shù)據(jù)源支持異步查詢, 查詢方法會(huì)立即返回 , 但是不會(huì)立即出結(jié)果 。

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

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

@Async
ListenableFuture<User> findOneByLastname(String lastname);    

Use java.util.concurrent.Future as the return type.
Use a Java 8 java.util.concurrent.CompletableFuture as the return type.
Use a org.springframework.util.concurrent.ListenableFuture as the return type.

創(chuàng)建Repository 實(shí)例

定義完repository接口后, 需要定義該接口實(shí)例 。 一種方法是使用spring data module提供的名稱空間配置, 但我們建議使用java configuration 。

xml 配置

每個(gè)spring data module 都包含一個(gè) repositories 元素 , 它定義一個(gè)將掃描的base package 。

<?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 將會(huì)掃描com.acme.repositories 及其子package ,尋找repository及其子接口。 對(duì)于每個(gè)找到的接口 , spring 都會(huì)使用特定數(shù)據(jù)源的FactoryBean 來創(chuàng)建合適的代理。 每個(gè)bean都會(huì)注冊(cè)一個(gè)同接口名的bean 。 例如 UserRepository 接口的bean名即為UserRepository 。 另外 base-package屬性支持通配符。

使用過濾的例子:

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

支持 <include-filter /> and <exclude-filter /> 過濾 。

javaConfig

在類上使用注解 @Enable${store}Repositories 可以同樣實(shí)例化repository 。

如下例子 配置一個(gè)JPA:

@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {

  @Bean
  EntityManagerFactory entityManagerFactory() {
    // …
  }
}

@Configuration @Bean

@Bean注解的角色與 xml中配置<bean>標(biāo)簽的角色一樣。 其作用的方法上, 指示方法實(shí)例化、配置、初始化由Spring IoC容器管理的新對(duì)象。
@Bean可與Spring @Component一起使用,但是,它們最常用于@Configuration bean。

@Configuration 注解使用在類上 , 作為bean的定義源。在類中通過簡單調(diào)用@Bean方法來表示bean間依賴關(guān)系。

如:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

等同于xml配置如下:
<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

當(dāng)@Bean 與 @Configuration一起使用時(shí), @Bean可以定義bean間的依賴。
但當(dāng)@Bean不與@Configuration一起使用時(shí), 只是使用了Bean 工廠,不定義依賴。

獨(dú)立使用

可以在Spring 容器之外使用repository 。 例如, 在CDI環(huán)境 。 但仍然需要spring librries。 spring data模塊提供特定數(shù)據(jù)源的RepositoryFactory 以支持repository。 例:

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

自定義實(shí)現(xiàn)repository

  1. 首選自定義一個(gè)接口 , 如
interface CustomizedUserRepository {
  void someCustomMethod(User user);
}
  1. 實(shí)現(xiàn)該接口 , 接口實(shí)現(xiàn)類必須以Impl結(jié)尾
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}
  1. 定義repository時(shí)繼承標(biāo)準(zhǔn)的repository和該自定義的
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {

  // Declare query methods here
}

這樣客戶端即可使用自定義實(shí)現(xiàn)的方法了 。

(當(dāng)然也 同時(shí)支持多個(gè)自定義repository)

優(yōu)先級(jí)

自定義方法的優(yōu)先級(jí)高于通用repository 和 特定存儲(chǔ)庫提供的。

可以在自定義中覆蓋通用或特定存儲(chǔ)庫的方法。

xml配置

當(dāng)使用xml配置時(shí), 同樣遵循impl后綴原則 。 spring會(huì)掃描base-package下的自定義實(shí)現(xiàn)。

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

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

可以通過配置修改默認(rèn)后綴, 如上。

歧義處理

若多個(gè)相同類名的 實(shí)現(xiàn)在不同的package中發(fā)現(xiàn) , spring data 通過bean名稱來確定使用哪一個(gè)。
例:

package com.acme.impl.one;

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  // Your custom implementation
}

package com.acme.impl.two;

@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  // Your custom implementation
}

如上有CustomizedUserRepository 的兩個(gè)實(shí)現(xiàn), 則第一個(gè)實(shí)現(xiàn)的bean名符合約定, 使用之。

自定義base repository

如果需要為所有的repository添加base實(shí)現(xiàn), 可以通過實(shí)現(xiàn)特定數(shù)據(jù)源的repository來實(shí)現(xiàn)。

class MyRepositoryImpl<T, ID extends Serializable>
  extends SimpleJpaRepository<T, ID> {

  private final EntityManager entityManager;

  MyRepositoryImpl(JpaEntityInformation entityInformation,
                          EntityManager entityManager) {
    super(entityInformation, entityManager);

    // Keep the EntityManager around to used from the newly introduced methods.
    this.entityManager = entityManager;
  }

  @Transactional
  public <S extends T> S save(S entity) {
    // implementation goes here
  }
}

配置時(shí)需要使用

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

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

Aggregate roots 事件發(fā)布

在Domain-Driven Design 應(yīng)用中, 聚合根發(fā)布domain 事件。
在domain類的方法中使用。
使用@DomainEvents 發(fā)布事件
使用@AfterDomainEventPublication 清理事件

這些方法會(huì)在調(diào)用save時(shí)被調(diào)用。

Spring data 擴(kuò)展

目前,大多數(shù)擴(kuò)展都是擴(kuò)展spring mvc。

Querydsl擴(kuò)展

Querydsl是一個(gè)框架,可以通過其流暢的API構(gòu)建靜態(tài)類型的SQL類查詢。

有幾款spring data module 通過 QuerydslPredicateExecutor 提供 Querydsl的整合。 如下:

public interface QuerydslPredicateExecutor<T> {

  Optional<T> findById(Predicate predicate);  

  Iterable<T> findAll(Predicate predicate);   

  long count(Predicate predicate);            

  boolean exists(Predicate predicate);        

  // … more functionality omitted.
}
  • Finds and returns a single entity matching the Predicate.
  • Finds and returns all entities matching the Predicate.
  • Returns the number of entities matching the Predicate.
  • Returns whether an entity that matches the Predicate exists.

然后通過繼承QuerydslPredicateExecutor 來使用之:

interface UserRepository extends CrudRepository<User, Long>, QuerydslPredicateExecutor<User> {
}

使用如下:

Predicate predicate = user.firstname.equalsIgnoreCase("dave")
    .and(user.lastname.startsWithIgnoreCase("mathews"));

userRepository.findAll(predicate);

Web 支持

通過在configurationclass 上添加注解 @EnableSpringDataWebSupport 來支持:

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}

@EnableSpringDataWebSupport 會(huì)自動(dòng)添加幾個(gè)組件。 另外他還會(huì)檢測classpath上是否有HATEOAS , 有則自動(dòng)注冊(cè)之。

若使用基于xml的配置, 見下:

<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />

<!-- If you use Spring HATEOAS, register this one *instead* of the former -->
<bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />

基礎(chǔ)web 支持

  • DomainClassConverter : 讓Spring MVC從請(qǐng)求參數(shù)或路徑變量中解析domain類的實(shí)例。
  • HandlerMethodArgumentResolver : 讓spring mvc 從請(qǐng)求參數(shù)中解析Pageable 和Sort實(shí)例。

DomainClassConverter

DomainClassConverter 允許在spring mvc中直接使用domain類, 而不用手工查找:

@Controller
@RequestMapping("/users")
class UserController {

  @RequestMapping("/{id}")
  String showUserForm(@PathVariable("id") User user, Model model) {

    model.addAttribute("user", user);
    return "userForm";
  }
}

該方法接收一個(gè)User實(shí)例,spring mvc 通過解析請(qǐng)求信息為id類型,然后調(diào)用findById方法。

HandlerMethodArgumentResolvers

web支持會(huì)同時(shí)注冊(cè)PageableHandlerMethodArgumentResolver 和 SortHandlerMethodArgumentResolver 。 這樣controller就可以使用Pageable 和sort。

例 :

@Controller
@RequestMapping("/users")
class UserController {

  private final UserRepository repository;

  UserController(UserRepository repository) {
    this.repository = repository;
  }

  @RequestMapping
  String showUsers(Model model, Pageable pageable) {

    model.addAttribute("users", repository.findAll(pageable));
    return "users";
  }
}

例中, spring mvc 將請(qǐng)求參數(shù)轉(zhuǎn)換為Pageable ,涉及如下參數(shù):

  • page : 想要獲取的頁碼,從0開始 ,默認(rèn)為0
  • size : 每頁條數(shù) , 默認(rèn)20
  • sort : 排序?qū)傩?,以property的格式 property,property(,ASC|DESC) 。默認(rèn)是升序 。 支持多個(gè)參數(shù),如: ?sort=firstname&sort=lastname,asc

要自定義該行為, 通過實(shí)現(xiàn)接口 PageableHandlerMethodArgumentResolverCustomizer 和 SortHandlerMethodArgumentResolverCustomizer , 實(shí)現(xiàn)其中的customize()方法。

若修改MethodArgumentResolver 滿足不了需求, 可以通過繼承SpringDataWebConfiguration 或 HATEOAS-enabled equivalent , 重寫 pageableResolver() or sortResolver() 方法 ,導(dǎo)入自定義配置而不是使用@Enable注解。

若需要從request中解析出多組Pageable 或sort ,如多table情況, 可以使用spring 的@Qualifier標(biāo)簽。 同時(shí)request的參數(shù)前需要加${qualifier}_前綴 。 例:

String showUsers(Model model,
      @Qualifier("thing1") Pageable first,
      @Qualifier("thing2") Pageable second) { … }

默認(rèn)傳遞到方法中的pageable 等同于 new PageRequest(0, 20) , 可以通過在Pageable參數(shù)上加注解@PageableDefault 來自定義 。

超媒體分頁

Spring HATEOAS 提供PagedResources類來豐富分頁信息。
將Page轉(zhuǎn)換為PagedResources 由PagedResourcesAssembler 實(shí)現(xiàn)。

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

web 數(shù)據(jù)綁定

spring data 的projections(預(yù)測)可以用來綁定請(qǐng)求數(shù)據(jù), 例子:

@ProjectedPayload
public interface UserPayload {

  @XBRead("http://firstname")
  @JsonPath("$..firstname")
  String getFirstname();

  @XBRead("/lastname")
  @JsonPath({ "$.lastname", "$.user.lastname" })
  String getLastname();
}

對(duì)于spring mvc , 只要@EnableSpringDataWebSupport注解激活并且類路徑上有相關(guān)的依賴, 則必要的轉(zhuǎn)換器會(huì)自動(dòng)被注冊(cè) 。 要使用RestTemplate ,則需要手工注冊(cè) ProjectingJackson2HttpMessageConverter (JSON) or XmlBeamHttpMessageConverter 。

repository populators

當(dāng)使用spring data jdbc模塊時(shí), 我們熟悉使用sql語言來與datasource交互 。 作為repository 級(jí)別的抽象, 不使用sql作為定義語言,因?yàn)樗麄兪且蕾嚁?shù)據(jù)源的。
populator 是支持json 和xml的 。

如有如下data.json文件:

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

你可以使用repository命名空間的populator來操作repository 。 聲明如下:

<?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的ObjectMapper來讀取并序列化。

Json根據(jù)文件中的_class 來確定序列化的對(duì)象。

下例顯示如何聲明使用JAXB來處理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: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>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容