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


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

  • 翻譯
    categories: 翻譯

說明:本翻譯4.64.7段發(fā)布在并發(fā)編程網(wǎng),其他段落為了熟悉上下文而翻譯挎袜,沒有精校袁稽。首次參與翻譯任務(wù)贩虾,翻譯的不好請(qǐng)指正由驹。


第四章 使用Spring Data Repositories

Spring Data repository abstraction目的是為了顯著的簡(jiǎn)化必要的樣板式代碼來為多種持久化數(shù)據(jù)存儲(chǔ)實(shí)現(xiàn)數(shù)據(jù)層圆到。

重要

本章解釋Spring Data repositories核心的概念和接口。Spring Data repository documentation 與 你的模塊雏亚。本章這些信息是從Spring Data Commons模塊獲取的厂财。它使用了JPA模塊的配置和代碼示范,命名引用覆蓋XML配置,它支持所有的Spring Data模塊支持的repository API痒给,Repository查詢關(guān)鍵字覆蓋被repository 接口一般的關(guān)鍵字查詢方法说墨。你的模塊特性的細(xì)節(jié)信息,查詢文檔對(duì)應(yīng)模塊苍柏。

4.1 核心概念

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

CrudRepository為被管理的實(shí)體類提供復(fù)雜CRUD功能棺棵。

4.2 查詢方法

標(biāo)準(zhǔn)的CRUD功能repositories通常有查詢底層數(shù)據(jù)庫楼咳。

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

  1. 聲明一個(gè)接口繼承Repository或者它的一個(gè)子類烛恤,并且指定要被處理的實(shí)體類和Id類型母怜。

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

    interface PersonRepository extends Repository<Person, Long> {
      List<Person> findByLastname(String lastname);
    }
    
  1. 創(chuàng)建Spring生成代理實(shí)現(xiàn)上面接口缚柏,通過JavaConfig:

    import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
    @EnableJpaRepositories
    class Config {}
    

    或者通過XML配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/data/jpa
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    <jpa:repositories base-package="com.acme.repositories"/>
    </beans>
    

    這個(gè)實(shí)例中使用了JPA命名空間苹熏。

    如果你為其他存儲(chǔ)使用repository接口,你需要修改來適應(yīng)你的存儲(chǔ)模塊命名空間的聲明币喧,大概就是替換jpa為期望的轨域,例如mongodb。

    當(dāng)然杀餐,注意JavaConfig并沒有明確配置一個(gè)包默認(rèn)使用注解的包干发。

    為了定制包被掃描可以使用數(shù)據(jù)存儲(chǔ)注解@Enable的一個(gè)屬性basePackage

  1. 獲得repository實(shí)例注入并使用怜浅。

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

以下部分詳細(xì)說明每個(gè)步驟铐然。

4.3 定義repository接口

第一步定義一個(gè)實(shí)體類依賴repository接口蔬崩。

這個(gè)接口必須繼承Repository接口并且指定實(shí)體類型和Id類型恶座。

如果你希望實(shí)體類型擁有CRUD方法,將Repository接口替換成繼承CrudRepository沥阳。

4.3.1 小巧repository定義

一般情況下跨琳,你的repository接口應(yīng)該繼承RepositoryCrudRepository或者PagingAndSortingRepository

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

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

如果你希望選擇方法,簡(jiǎn)單的從CurdRepository復(fù)制你希望獲得的方法到你的Repository

注意

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

例5.有選擇的展現(xiàn)CRUD方法

@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
  T findOne(ID id);
  T save(T entity);
}

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

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

這些方法會(huì)被轉(zhuǎn)發(fā)到你選擇的Spring Data提供的基本repository實(shí)現(xiàn),例如JPASimpleJpaRepository,因?yàn)樗麄兤ヅ?code>CrudRepository的方法簽名.

因此UserPepository現(xiàn)在可以保存用戶,查找唯一用戶根據(jù)id,并且觸發(fā)一個(gè)查詢?nèi)ゲ檎?code>Users根據(jù)他們的郵箱地址

注意

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

對(duì)所有Spring Data在運(yùn)行時(shí)不需要生成實(shí)例的repository接口,確保你為他們添加了注解.

4.3.2 在多個(gè)Spring Data模塊使用Repositories

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

有時(shí)候應(yīng)用需要使用不止一個(gè)Spring Data模塊.

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

在class path發(fā)現(xiàn)多個(gè)repository工廠時(shí),Spring Data嚴(yán)格檢測(cè)repository配置模塊.

在repository或者實(shí)體類嚴(yán)格配置需要得細(xì)節(jié)以決定Spring Data模塊綁定一個(gè)repository的定義:

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

    Spring Data模塊接收第三方注解(例如 JPA的@Entity)或者提供自己的注解例如@Document為Spring Data MongoDB/Spring Data Elasticsearch.

例6.使用模塊特有的接口定義Repository

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

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

MyRepositoryUserRepository繼承JpaRepository在他們類型層級(jí).他們有效的表示了Spring Data JPA模塊.

例7.使用通用的接口定義Repository.

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

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

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

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

例8.使用注解配合實(shí)體類定義Repositor

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

@Entity
public class Person {
  …
}

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

@Document
public class User {
  …
}

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

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

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

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

@Entity
@Document
public class Person {
  …
}

這個(gè)示例展示實(shí)體類同時(shí)使用JPA和Spring Data MongoDB注解.

這里定義了兩個(gè)repository,JpaPersonRepositoryMongoDBPersonRepository.

一個(gè)被JPA使用,另一個(gè)被MongoDB使用.

Spring Data不再能告訴repository區(qū)分,這將導(dǎo)致不清晰的行為

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

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

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

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

默認(rèn)的,注解配置使用類配置的包

基于XML基本配置在這里.

例10.注解配置基本包

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

4.4 Defining query methods

4.4 定義查詢方法

repository代理有兩種方式獲得通過方法名獲得指定查詢.可以通過方法名直接獲得查詢,或者手動(dòng)定義查詢.有可用的選項(xiàng)定義在實(shí)際的存儲(chǔ).這有一些策略決定實(shí)際查詢?nèi)绾伪粍?chuàng)建.讓我們一些看看可用的選項(xiàng).

4.4.1 Query lookup strategies

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

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

4.4.2 查詢創(chuàng)建

建成Spring Data repository基本組件的查詢構(gòu)建機(jī)制有用的構(gòu)建了repository所有實(shí)體類的約束查詢.

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

例11.來自方法名的查詢創(chuàng)建

public interface PersonRepository extends Repository<User, Long> {
  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String
                                             lastname);
  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String
                                                       firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String
                                                       firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String
                                                       firstname);
  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
 
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

解析方法的實(shí)際結(jié)果取決于你創(chuàng)建查詢的持久化存儲(chǔ).然而,這有些問題需要注意:

  • 表達(dá)式通常串行的連接屬性的遍歷與操作符.你可以使用and和or連接屬性表達(dá)式.你也可以給屬性表達(dá)式使用操作符,例如between,lessThan,granterThan,like.你可以使用的表達(dá)式操作符有between,LessThan,GreaterThan,Loke.被支持的操作符根據(jù)多樣的數(shù)據(jù)庫決定,因此查詢你引用文檔的恰當(dāng)部分.
  • 方法解析支持為單獨(dú)屬性設(shè)置一個(gè)IgnoreCase標(biāo)志(例如,findByLastnameIgnoreCase(...))或者為所有屬性都支持忽略情況(通常是String情形,例如,findByLastnameAndFirstnameAllIgnoreCase(…)).忽略情況是否被支持由多樣的數(shù)據(jù)庫決定脉让,所以具體存儲(chǔ)查詢方法在引用文檔查詢相關(guān)章節(jié).
  • 你可以為查詢方法引用的屬性提供靜態(tài)排序連接OrderBy子句并且提供排序方向(Asc或Desc).為創(chuàng)建一個(gè)查詢方法支持動(dòng)態(tài)排序,查看特殊參數(shù)處理.

4.4.3 屬性表達(dá)式

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

List<Person> findByAddressZipCode(ZipCode zipCode);

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

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

沒了解決這種起義,你可以在方法名稱內(nèi)使用_手動(dòng)的定義遍歷點(diǎn).所以我們的方法名稱最終像這樣:

List<Person> findByAddress_ZipCode(ZipCode zipCode);

As we treat underscore as a reserved character we strongly advise to follow standard Java naming
conventions (i.e. not using underscores in property names but camel case instead)

我們對(duì)待下劃線當(dāng)做一個(gè)保留關(guān)鍵字,我們強(qiáng)力建議遵循標(biāo)準(zhǔn)Java命名規(guī)范(例如,使用駝峰命名而不是下劃線命名屬性名稱)

4.4.4 特殊參數(shù)處理

處理你查詢中的參數(shù),你可以定義簡(jiǎn)單的方法參數(shù)像上面的示例中.處理之外基本組件可以識(shí)別出某些特殊類型例如PageableSort用來動(dòng)態(tài)的編碼和排序你的查詢.

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

第一個(gè)方法允許你在通過一個(gè)org.springframework.data.domain.Pageable實(shí)例在查詢方法中動(dòng)態(tài)添加分頁信息在你的靜態(tài)定義查詢中.一個(gè)Page清楚數(shù)據(jù)的全部數(shù)量和頁面總數(shù).它是通過觸發(fā)計(jì)數(shù)的基礎(chǔ)設(shè)施查詢計(jì)算總數(shù)溅潜。這個(gè)代價(jià)可能是昂貴的具體取決于所使用的存儲(chǔ),可以用Slice代替.一個(gè)Slict只知道下一個(gè)Slice可到到哪里,當(dāng)運(yùn)行在一個(gè)大的結(jié)果集上時(shí)這可能已經(jīng)足夠了.排序選項(xiàng)也通過Pageable實(shí)例處理.如果你只需要排序,簡(jiǎn)單起見添加org.springframework.data.domain.Sort參數(shù)在你的方法.如你所見,簡(jiǎn)單返回一個(gè)列表.在這種情況下,額外元數(shù)據(jù)構(gòu)建實(shí)際的Page實(shí)例將不會(huì)被創(chuàng)建(反過來這意味著,沒有發(fā)出額外的統(tǒng)計(jì)查詢所必須的)簡(jiǎn)單約束只在給定的范圍內(nèi)查詢.

注意

為了盡早得到你查詢了多少頁,你必須出發(fā)一個(gè)格外統(tǒng)計(jì)查詢.默認(rèn)的,這個(gè)查詢你實(shí)際觸發(fā)的查詢調(diào)用.

4.4.5 限制查詢結(jié)果

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

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

User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);

限制表達(dá)式也支持Distinct關(guān)鍵字.此外,對(duì)于將結(jié)果集設(shè)置為一個(gè)實(shí)例的查詢薪伏,支持將結(jié)果包裝到一個(gè)Optional.如果應(yīng)用分頁或分片限制查詢分頁(并且計(jì)算可用的頁數(shù))那么這可以應(yīng)用limited結(jié)果.

注意

請(qǐng)注意滚澜,限制結(jié)果結(jié)合使用Sort的動(dòng)態(tài)排序的結(jié)果允許參數(shù)可以表示“k”最小的查詢方法以及“K”的查詢方法最大的元素。

4.4.6 流式處理結(jié)果

方法查詢結(jié)果可以通過使用java 8Stream<T>逐步處理嫁怀。

特定的方法用來表示流而不是簡(jiǎn)單的包裝查詢結(jié)果在一個(gè)Stream數(shù)據(jù)存儲(chǔ)

例14 一個(gè)用java流8Stream<T>查詢結(jié)果

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

Stream<User> readAllByFirstnameNotNull();

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

注意

一個(gè)Stream潛在的包裝底層數(shù)據(jù)存儲(chǔ)在頁數(shù)的資源中,因此使用完畢必須關(guān)閉.你可以使用close()方法手動(dòng)關(guān)閉Stream或者使用一個(gè)Java7的try_with-resources塊.

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

4.4.7 異步查詢結(jié)果

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

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

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

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

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

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

4.5.1 XML配置

每個(gè)Spring Data module包含一個(gè)repository 元素,這可以讓你簡(jiǎn)單的定義一個(gè)基本包,Spring為你掃描它.

例16 通過XML啟用Spring Data repositories

<?xml version="1.0" encoding="UTF-8"?>
    <beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://www.springframework.org/schema/data/jpa"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/data/jpa
        http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
      <repositories base-package="com.acme.repositories" />
    </beans:beans>

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

使用過濾

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

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

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

例17. 使用exclude-filter元素

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

這個(gè)示例從待實(shí)例化對(duì)象中排除所有以SomeRepository結(jié)尾的接口.

4.5.2 JavaConfig

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

一個(gè)簡(jiǎn)單配置啟用Spring Data repositories像這樣:

例18. repository配置基于簡(jiǎn)單注解

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

注意

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

4.5.3 單獨(dú)使用

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

例19. repository工廠單獨(dú)使用

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

4.6 定制Spring Data倉庫實(shí)現(xiàn)

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

4.6.1 為單獨(dú)倉庫添加定制行為

為了定制功能豐富一個(gè)倉庫,你首先為定制功能定義一個(gè)接口和實(shí)現(xiàn).使用你提供的倉庫接口來繼承自定義接口.

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

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

例21.定制功能的實(shí)現(xiàn)

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

注意

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

實(shí)現(xiàn)的本身沒有依賴Spring Data,可以是一個(gè)標(biāo)準(zhǔn)的Spring bean.所以你可以使用標(biāo)準(zhǔn)的依賴注入行為給其他bean注入引用,像JdbcTemplate,切面的一部分等等.

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

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

讓你的標(biāo)準(zhǔn)倉庫接口繼承定制的.這樣做結(jié)合了CRUD和定制功能并使其可用于客戶端.

配置

如果你使用命名空間配置,倉庫基本組件掃描類所在包,根據(jù)掃描結(jié)果嘗試自動(dòng)定制實(shí)現(xiàn).

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

例23. 配置示例

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

第一個(gè)配置示例將查實(shí)查找一個(gè)類com.acme.repository.UserRepositoryImpl來作為定制藏局實(shí)現(xiàn).然而第二個(gè)示例將嘗試查找com.acme.repository.UserRepositoryFooBar.

手動(dòng)指定

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

例24.手動(dòng)指定定制實(shí)現(xiàn)

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

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

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

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

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

現(xiàn)在你獨(dú)立的倉庫接口將繼承這個(gè)中間接口而不是Repository接口來包含功能的定義.接下來創(chuàng)建一個(gè)中間接口的實(shí)現(xiàn)繼承持久化具體倉庫的基本類.這個(gè)類后面將作為倉庫代理的基本類.

例26 定制倉庫基本類

public class MyRepositoryImpl<T, ID extends Serializable>
  extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
  
  private final EntityManager entityManager;
  
  public MyRepositoryImpl(JpaEntityInformation entityInformation,
                          EntityManager entityManager) {
    super(entityInformation, entityManager);
    // Keep the EntityManager around to used from the newly introduced methods.
    this.entityManager = entityManager;
  }
  
  public void sharedCustomMethod(ID id) {
    // implementation goes here
  }
}

警告

這個(gè)類需要有一個(gè)構(gòu)造方法調(diào)用父類具體存儲(chǔ)倉庫工廠實(shí)現(xiàn).萬一倉庫基礎(chǔ)類有多個(gè)構(gòu)造,覆蓋包括一個(gè)EntityInformation加上存儲(chǔ)具體基本組件對(duì)象(例如一個(gè)EntityManager或者模板類)

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

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

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

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

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

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

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

4.7 Spring Data擴(kuò)展

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

4.7.1 Querydsl擴(kuò)展

Querydsl是一個(gè)框架,通過它的流式API構(gòu)建靜態(tài)類型的SQL類查詢.

多個(gè)Spring Data模塊通過QueryDslPredicateExecutor與Querydsl集成.

例29 QueryDslPredicateExecutor接口

public interface QueryDslPredicateExecutor<T> {
  T findOne(Predicate predicate); ①
    Iterable<T> findAll(Predicate predicate); ②
    long count(Predicate predicate); ③
    boolean exists(Predicate predicate); ④
    // … more functionality omitted.
}

① 查詢并返回一個(gè)匹配Predicate的單例實(shí)體

②查詢并返回所有匹配Predicate的實(shí)體

③ 返回匹配Predicate的實(shí)體數(shù)量

④ 返回是否存在一個(gè)匹配Predicate的實(shí)體

為了簡(jiǎn)單的使用Querydsl功能,在你的倉庫接口繼承QueryDslPredicateExecutor.

例30 在倉庫集成QueryDsl

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

像上面這樣就可以使用Querydsl的Predicate書寫類型安全的查詢

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

4.7.2 Web支持

注意

本節(jié)包含Spring Data Web支持的文檔是在1.6范圍內(nèi)的Spring Data Commons實(shí)現(xiàn)的.因?yàn)橹С中乱氲膬?nèi)容改變了很多東西萝招,我們保留了舊行為的文檔在"遺留Web支持"部分.

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

例31 啟用Spring Data web支持

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
class WebConfiguration {}

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

作為可選項(xiàng),如果你使用XML配置,注冊(cè)SpringDataWebSupport或者HateoasWareSpringDataWebSupport作為Spring bean:

例32 用XML啟用Spring Data web支持

<bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
<!-- If you're using Spring HATEOAS as well register this one *instead* of the
former -->
<bean class= "org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />

基本W(wǎng)eb支持

上面展示的的配置設(shè)置將注冊(cè)幾個(gè)基本組件:

  • 一個(gè)DomainClassConverter啟用Spring MVC來根據(jù)請(qǐng)求參數(shù)或路徑變量管理倉例實(shí)體類的實(shí)例
  • HandlerMethodArgumentResolver實(shí)現(xiàn)讓Spring MVC從請(qǐng)求參數(shù)解析Pageable和Sort實(shí)例.

實(shí)體類轉(zhuǎn)換

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

例33 一個(gè)Spring MVC控制器在方法簽名中使用實(shí)體類型

@Controller
@RequestMapping("/users")
public class UserController {
  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {
    model.addAttribute("user", user);
    return "userForm";
  }
}

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

注意

當(dāng)前的倉庫必須實(shí)現(xiàn)CrudRepository做好準(zhǔn)備被發(fā)現(xiàn)來進(jìn)行轉(zhuǎn)換.

為了分頁和排序分解方法參數(shù)

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

例34 使用Pageable作為控制器方法參數(shù)

@Controller
@RequestMapping("/users")
public class UserController {
  @Autowired UserRepository repository;
  @RequestMapping
  public String showUsers(Model model, Pageable pageable) {
    model.addAttribute("users", repository.findAll(pageable));
    return "users";
  }
}

這個(gè)方法簽名將使Spring MVC嘗試使用下面的默認(rèn)配置從請(qǐng)求參數(shù)中轉(zhuǎn)換一個(gè)Pageable實(shí)例:

表1 請(qǐng)求參數(shù)轉(zhuǎn)換Pageable實(shí)例

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

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

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

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

你必須填充foo_page和bar_page等.

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

Hypermedia支持分頁

Spring HATEOAS包裝了一個(gè)代表模型的類PageResources ,

它可以使用Page實(shí)例包裝必要的Page元數(shù)據(jù)內(nèi)容作為連接讓客戶端導(dǎo)航頁面.一個(gè)頁面到一個(gè)PageResources的轉(zhuǎn)換被Spring HATEOAS的ResourceAssembler接口實(shí)現(xiàn)PagedResourcesAssembler來完成.

例35 使用一個(gè)PagedResourcesAssembler作為控制器方法參數(shù)

@Controller
class PersonController {
  @Autowired PersonRepository repository;
  @RequestMapping(value = "/persons", method = RequestMethod.GET)
  HttpEntity<PagedResources<Person>> persons(Pageable pageable,
                                             PagedResourcesAssembler assembler) {
    Page<Person> persons = repository.findAll(pageable);
    return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
  }
}

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

  • Page的內(nèi)容將PageResources實(shí)例的內(nèi)容
  • PageResources將獲得PageMetadata實(shí)例,該實(shí)例由Page和基礎(chǔ)的PageRequest中的信息填充
  • PageResources獲得prevnext連接,添加這些依賴在頁面.這些鏈接將指向uri方法的調(diào)用映射.頁碼參數(shù)根據(jù)PageableHandlerMethodArgumentResolver添加到參數(shù)以在后面被轉(zhuǎn)換.

假設(shè)我們有30個(gè)Person實(shí)例在數(shù)據(jù)庫.你現(xiàn)在可以觸發(fā)一個(gè)GET請(qǐng)求 http://localhost:8080/persons, 你將可以看到類似下面的內(nèi)容:

{ "links" : [ { "rel" : "next",
"href" : "http://localhost:8080/persons?page=1&size=20 }
],
"content" : [
… // 20 Person instances rendered here
],
"pageMetadata" : {
"size" : 20,
"totalElements" : 30,
"totalPages" : 2,
"number" : 0
}
}

你可以看到編譯生成了正確的URI,并且還會(huì)提取默認(rèn)配置轉(zhuǎn)換參數(shù)到即將到來的請(qǐng)求中的Pageable.這意味著,如果你改變配置,鏈接也將自動(dòng)跟隨改變.默認(rèn)情況下,編譯指向控制器執(zhí)行的方法岗钩,但是這可以被一個(gè)自定義鏈接作為基本構(gòu)建來構(gòu)成分頁的Link重載PagedResourcesAssembler.toResource(...)方法定制.

Querydsl web 支持

那些整合了QueryDSL的存儲(chǔ)可能從Request查詢字符串中的屬性驅(qū)動(dòng)查詢.

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

?firstname=Dave&lastname=Matthews

可以被轉(zhuǎn)換為

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

使用了QuerydslPredicateArgumentResolver.

注意

當(dāng)在類路徑上找到Querydsl時(shí)逸爵,該功能將在@EnableSpringDataWebSupport注解中自動(dòng)啟用

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

提示

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

@Controller
class UserController {
  @Autowired UserRepository repository;
  @RequestMapping(value = "/", method = RequestMethod.GET)
  String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,
              Pageable pageable, @RequestParam MultiValueMap<String, String>
    parameters) {
        model.addAttribute("users", repository.findAll(predicate, pageable));
        return "index";
  } 
}

為User轉(zhuǎn)換匹配查詢字符串參數(shù)的Predicate

默認(rèn)的綁定規(guī)則如下:

  1. Object在簡(jiǎn)單屬性上如同eq

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

  3. Collection在簡(jiǎn)單屬性上如同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將自動(dòng)注解@QuerydslPredicate(bindings=...)
  3. username屬性定義綁定,綁定到一個(gè)簡(jiǎn)單集合
  4. String屬性定義默認(rèn)綁定到一個(gè)不區(qū)分大小寫的集合
  5. Predicate移除密碼屬性

4.7.3 倉庫填充

如果你使用Spring JDBC模塊,你可能熟悉在DataSource使用SQL腳本來填充.一個(gè)類似的抽象在倉庫級(jí)別可以使用,盡管它不是使用SQL作為數(shù)據(jù)定義語言,因?yàn)樗仨氂纱鎯?chǔ)決定.填充根據(jù)倉庫支持XML(通過Spring的OXM抽象)和JSON(通過Jackson)定義數(shù)據(jù).

假設(shè)你有一個(gè)文件data.json內(nèi)容如下:

例36 JSON定義的數(shù)據(jù)

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

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

例37 聲明一個(gè)Jackson倉庫填充

  <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:repository="http://www.springframework.org/schema/data/repository"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/data/repository
        http://www.springframework.org/schema/data/repository/spring-repository.xsd">
      <repository:jackson2-populator locations="classpath:data.json" />
    </beans>

這樣的聲明可以讓data.json文件可以被一個(gè)Jackson的ObjectMpper讀取和反序列化.

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

要使用XML定義數(shù)據(jù)填充倉庫,你可以使用unmarshaller-populator元素.你配置它使用Spring OXM提供給你的XML裝配選項(xiàng).在Spring reference documentation查看更多細(xì)節(jié).

例38 聲明一個(gè)裝配倉庫填充器(使用JAXB)

<?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:repository="http://www.springframework.org/schema/data/repository"
      xmlns:oxm="http://www.springframework.org/schema/oxm"
      xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/data/repository
        http://www.springframework.org/schema/data/repository/spring-repository.xsd
        http://www.springframework.org/schema/oxm
        http://www.springframework.org/schema/oxm/spring-oxm.xsd">
      <repository:unmarshaller-populator locations="classpath:data.json"
        unmarshaller-ref="unmarshaller" />
      <oxm:jaxb2-marshaller contextPath="com.acme" />
    </beans>

4.7.4 遺留web支持

Spring MVC的實(shí)體類綁定

如果正在開發(fā)Spring MVC web應(yīng)用,你通常必須從URL中解析實(shí)體類的id.默認(rèn)的,你的任務(wù)是轉(zhuǎn)化請(qǐng)求參數(shù)或URL參數(shù)到實(shí)體類并將它移交給下面或直接在實(shí)體上操作業(yè)務(wù)邏輯.這看起來像下面這樣:

@Controller
@RequestMapping("/users")
public class UserController {
  private final UserRepository userRepository;
  
  @Autowired
  public UserController(UserRepository userRepository) {
    Assert.notNull(repository, "Repository must not be null!");
    this.userRepository = userRepository;
  }

  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") Long id, Model model) {
    // Do null check for id
    User user = userRepository.findOne(id);
    // Do null check for user
    model.addAttribute("user", user);
    return "user";
  }
}

首先你為每個(gè)控制器定義一個(gè)依賴的倉庫來查找它們分別管理的實(shí)體.查詢實(shí)體也是樣板,因?yàn)樗偸且粋€(gè)findOne(...)調(diào)用.幸運(yùn)的Spring提供了方法來注冊(cè)自定義組件,允許一個(gè)String值轉(zhuǎn)換到一個(gè)屬性類型.

屬性編輯

Spring3.0之前JavaPropertyEditors被使用.為了集成這些,Spring Data提出一個(gè)DomainClassPropertyEditorRegistrar來查詢所有注冊(cè)到ApplicatonContext的Spring Data倉庫和一個(gè)定制的PropertyEditor來管理實(shí)體類.

<bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
  <property name="webBindingInitializer">
    <bean class="….web.bind.support.ConfigurableWebBindingInitializer">
      <property name="propertyEditorRegistrars">
        <bean class=
          "org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" />
      </property>
    </bean>
  </property>
</bean>

如果你已經(jīng)像上面這樣配置Spring MVC,你可以向下面這樣配置你的控制器,從而減少不清晰和樣板式的代碼

@Controller
@RequestMapping("/users")
public class UserController {
  @RequestMapping("/{id}")
  public String showUserForm(@PathVariable("id") User user, Model model) {
    model.addAttribute("user", user);
    return "userForm";
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市凹嘲,隨后出現(xiàn)的幾起案子师倔,更是在濱河造成了極大的恐慌,老刑警劉巖周蹭,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件趋艘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡凶朗,警方通過查閱死者的電腦和手機(jī)瓷胧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棚愤,“玉大人搓萧,你說我怎么就攤上這事⊥鹌瑁” “怎么了瘸洛?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長次和。 經(jīng)常有香客問我反肋,道長,這世上最難降的妖魔是什么踏施? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任石蔗,我火速辦了婚禮,結(jié)果婚禮上畅形,老公的妹妹穿的比我還像新娘养距。我一直安慰自己,他們只是感情好日熬,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布棍厌。 她就那樣靜靜地躺著,像睡著了一般碍遍。 火紅的嫁衣襯著肌膚如雪定铜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天怕敬,我揣著相機(jī)與錄音揣炕,去河邊找鬼。 笑死东跪,一個(gè)胖子當(dāng)著我的面吹牛畸陡,可吹牛的內(nèi)容都是我干的鹰溜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼丁恭,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼曹动!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牲览,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤墓陈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后第献,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贡必,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年庸毫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了仔拟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡飒赃,死狀恐怖利花,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情载佳,我是刑警寧澤炒事,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站刚盈,受9級(jí)特大地震影響羡洛,放射性物質(zhì)發(fā)生泄漏挂脑。R本人自食惡果不足惜藕漱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望崭闲。 院中可真熱鬧肋联,春花似錦、人聲如沸刁俭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牍戚。三九已至侮繁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間如孝,已是汗流浹背宪哩。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留第晰,地道東北人锁孟。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓彬祖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親品抽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子储笑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評(píng)論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)圆恤,斷路器突倍,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,313評(píng)論 1 92
  • Spring Data(一)概念和倉庫的定義 Spring Data的主要任務(wù)是為數(shù)據(jù)訪問提供一個(gè)相似的盆昙、一致的赘方、...
    牛初九閱讀 936評(píng)論 1 5
  • 為什么需要空安全 在java中我們經(jīng)常會(huì)因?yàn)橐粋€(gè)變量沒能正確判斷是否非空而導(dǎo)致程序崩潰,java中需要我們?nèi)プ约号?..
    劉岳森閱讀 917評(píng)論 0 1