核心概念
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è)步驟:
- 聲明一個(gè)接口, 該接口需要繼承Repository 或其子接口 ,并提供domain 類和id參數(shù), 如下:
interface PersonRepository extends Repository<Person, Long> { … }
- 在該接口中聲明查詢方法:
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}
- 使用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屬性。
- 注入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)行判斷:
- repository 是否繼承自特定數(shù)據(jù)源, 將根據(jù)特定數(shù)據(jù)源進(jìn)行判斷。
- 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
- 首選自定義一個(gè)接口 , 如
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
- 實(shí)現(xiàn)該接口 , 接口實(shí)現(xiàn)類必須以Impl結(jié)尾
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
- 定義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>