天天看點

Spring Data 官方文檔》4.7 Spring Data擴充

這部分說明spring data一系列的擴充功能,可以使spring dta使用多樣的上下文.目前大部分內建是針對spring mvc.

querydsl是一個架構,通過它的流式api建構靜态類型的sql類查詢。多個spring data子產品通過querydslpredicateexecutor與querydsl內建。

例29 querydslpredicateexecutor接口

<code>1</code>

<code>public</code> <code>interface</code> <code>querydslpredicateexecutor&lt;t&gt; {</code>

<code>2</code>

<code>  </code><code>t findone(predicate predicate); ①</code>

<code>3</code>

<code>    </code><code>iterable&lt;t&gt; findall(predicate predicate); ②</code>

<code>4</code>

<code>    </code><code>long</code> <code>count(predicate predicate); ③</code>

<code>5</code>

<code>    </code><code>boolean</code> <code>exists(predicate predicate); ④</code>

<code>6</code>

<code>    </code><code>// … more functionality omitted.</code>

<code>7</code>

<code>}</code>

① 查詢并傳回一個比對predicate的單例實體

②查詢并傳回所有比對predicate的實體

③ 傳回比對predicate的實體數量

④ 傳回是否存在一個比對predicate的實體

為了簡單的使用querydsl功能,在你的倉庫接口繼承querydslpredicateexecutor.

例30 在倉庫內建querydsl

<code>interface</code> <code>userrepository </code><code>extends</code> <code>crudrepository&lt;user, long&gt;,</code>

<code>querydslpredicateexecutor&lt;user&gt; {</code>

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

<code>predicate predicate = user.firstname.equalsignorecase(</code><code>"dave"</code><code>).and(user.lastname.startswithignorecase(</code><code>"mathews"</code><code>));</code>

<code>userrepository.findall(predicate);</code>

注意 本節包含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支援

<code>@configuration</code>

<code>@enablewebmvc</code>

<code>@enablespringdatawebsupport</code>

<code>class</code> <code>webconfiguration {}</code>

@enablespringdatawebsupport注解注冊了一些元件,我們将在稍後讨論.注解還将在類路徑上檢測spring hateoas,如果才在将為其注冊內建元件.

作為可選項,如果你使用xml配置,注冊springdatawebsupport或者hateoaswarespringdatawebsupport作為spring bean:

例32 用xml啟用spring data web支援

<code>&lt;</code><code>bean</code> <code>class</code><code>=</code><code>"org.springframework.data.web.config.springdatawebconfiguration"</code> <code>/&gt;</code>

<code>&lt;!-- if you're using spring hateoas as well register this one *instead* of the former --&gt;</code>

<code>&lt;</code><code>bean</code> <code>class</code><code>= </code><code>"org.springframework.data.web.config.hateoasawarespringdatawebconfiguration"</code><code>/&gt;</code>

基本web支援

上面展示的的配置設定将注冊幾個基本元件:

一個domainclassconverter啟用spring mvc來根據請求參數或路徑變量管理倉例實體類的執行個體

handlermethodargumentresolver實作讓spring mvc從請求參數解析pageable和sort執行個體

實體類轉換

domainclassconverter允許你在spring mvc控制器方法簽名中直接使用實體類型,是以你不必手動的通過倉庫查詢執行個體:

例33 一個spring mvc控制器在方法簽名中使用實體類型

<code>@controller</code>

<code>@requestmapping</code><code>(</code><code>"/users"</code><code>)</code>

<code>public</code> <code>class</code> <code>usercontroller {</code>

<code>  </code><code>@requestmapping</code><code>(</code><code>"/{id}"</code><code>)</code>

<code>  </code><code>public</code> <code>string showuserform(</code><code>@pathvariable</code><code>(</code><code>"id"</code><code>) user user, model model) {</code>

<code>    </code><code>model.addattribute(</code><code>"user"</code><code>, user);</code>

<code>    </code><code>return</code> <code>"userform"</code><code>;</code>

<code>8</code>

<code>  </code><code>}</code>

<code>9</code>

如你所見,方法直接接收一個user執行個體并沒有更進一步的查詢是否必要.執行個體可以通過spring mvc将路徑變量轉換為實體類的id類型并最終通過在實體類型注冊的倉庫執行個體上調用findone(…)通路執行個體轉換得到.

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

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

上面的配置片段還注冊了一個pageablehandlermethodargumentresolver和一個sorthandlermethodargumentresolver執行個體.注冊使得pageable和sort成為有效的控制器方法參數.

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

<code>01</code>

<code>02</code>

<code>03</code>

<code>04</code>

<code>  </code><code>@autowired</code> <code>userrepository repository;</code>

<code>05</code>

<code>  </code><code>@requestmapping</code>

<code>06</code>

<code>  </code><code>public</code> <code>string showusers(model model, pageable pageable) {</code>

<code>07</code>

<code>    </code><code>model.addattribute(</code><code>"users"</code><code>, repository.findall(pageable));</code>

<code>08</code>

<code>    </code><code>return</code> <code>"users"</code><code>;</code>

<code>09</code>

<code>10</code>

<code>}&lt;/blockquote&gt;</code>

這個方法簽名将使spring mvc嘗試使用下面的預設配置從請求參數中轉換一個pageable執行個體:

表1 請求參數轉換pageable執行個體

Spring Data 官方文檔》4.7 Spring Data擴充

為了定制行為,可以繼承springdatawebconfiguration或者啟用等效的hateoas并覆寫pageableresolver()或sortresolver()方法并導入你的自定義配置檔案替代@enable-注解.

有一種情況你需要多個pageable或sort執行個體從請求轉換(例如處理多個表單),你可以使用spring的@qualifier注解來互相差別.請求參數必須以${qualifier}為字首.這樣一個方法的簽名像這樣:

<code>public</code> <code>string showusers(model model,</code>

<code>                        </code><code>@qualifier</code><code>(</code><code>"foo"</code><code>)pagebale first,</code>

<code>                        </code><code>@qualifier</code><code>(</code><code>"bar"</code><code>) pageable second) {</code>

<code>  </code><code>...</code>

你必須填充foo_page和bar_page等.

預設的pageable在方法中處理等價于一個new pagerequest(0, 20),但是可以使用@pageabledefaults注解在pageable參數上定制.

hypermedia支援分頁

spring hateoas包裝了一個代表模型的類pageresources ,它可以使用page執行個體包裝必要的page中繼資料内容作為連接配接讓用戶端導航頁面.一個頁面到一個pageresources的轉換被spring hateoas的resourceassembler接口實作pagedresourcesassembler來完成.

例35 使用一個pagedresourcesassembler作為控制器方法參數

<code>class</code> <code>personcontroller {</code>

<code>  </code><code>@autowired</code> <code>personrepository repository;</code>

<code>  </code><code>@requestmapping</code><code>(value = </code><code>"/persons"</code><code>, method = requestmethod.get)</code>

<code>  </code><code>httpentity&lt;pagedresources&lt;person&gt;&gt; persons(pageable pageable,</code>

<code>                                             </code><code>pagedresourcesassembler assembler) {</code>

<code>    </code><code>page&lt;person&gt; persons = repository.findall(pageable);</code>

<code>    </code><code>return</code> <code>new</code> <code>responseentity&lt;&gt;(assembler.toresources(persons), httpstatus.ok);</code>

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

page的内容将pageresources執行個體的内容

pageresources将獲得pagemetadata執行個體,該執行個體由page和基礎的pagerequest中的資訊填充

pageresources獲得prev和next連接配接,添加這些依賴在頁面.這些連結将指向uri方法的調用映射.頁碼參數根據pageablehandlermethodargumentresolver添加到參數以在後面被轉換

假設我們有30個person執行個體在資料庫.你現在可以觸發一個get請求 http://localhost:8080/persons, 你将可以看到類似下面的内容:

<code>{ "links" : [ { "rel" : "next",</code>

<code>],</code>

<code>"content" : [</code>

<code>… // 20 person instances rendered here</code>

<code>"pagemetadata" : {</code>

<code>"size" : 20,</code>

<code>"totalelements" : 30,</code>

<code>"totalpages" : 2,</code>

<code>11</code>

<code>"number" : 0</code>

<code>12</code>

<code>13</code>

你可以看到編譯生成了正确的uri,并且還會提取預設配置轉換參數到即将到來的請求中的pageable.這意味着,如果你改變配置,連結也将自動跟随改變.預設情況下,編譯指向控制器執行的方法,但是這可以被一個自定義連結作為基本建構來構成分頁的link重載pagedresourcesassembler.toresource(…)方法定制.

querydsl web 支援

那些整合了querydsl的存儲可能從request查詢字元串中的屬性驅動查詢.

這意味着前面例子的查詢字元串可以給出user的對象

<code>?firstname=dave&amp;lastname=matthews</code>

可以被轉換為

<code>quser.user.firstname.eq(</code><code>"dave"</code><code>).and(quser.user.lastname.eq(</code><code>"matthews"</code><code>))</code>

使用querydslpredicateargumentresolver.

當在類路徑上找到querydsl時,該功能将在@enablespringdatawebsupport注解中自動啟用

添加一個@querydslpredicate到一個方法簽名将提供一個就緒的predicate,可以通過querydslpredicateexecutor執行.

提示 類型資訊通常從傳回方法上解析.由于這些資訊不一定比對實體類型,使用querydslpredicate的root屬性可能是個好主意.

<code>class</code> <code>usercontroller {</code>

<code>  </code><code>@requestmapping</code><code>(value = </code><code>"/"</code><code>, method = requestmethod.get)</code>

<code>  </code><code>string index(model model, </code><code>@querydslpredicate</code><code>(root = user.</code><code>class</code><code>) predicate predicate,  ①</code>

<code>              </code><code>pageable pageable, </code><code>@requestparam</code> <code>multivaluemap&lt;string, string&gt;</code>

<code>    </code><code>parameters) {</code>

<code>        </code><code>model.addattribute(</code><code>"users"</code><code>, repository.findall(predicate, pageable));</code>

<code>        </code><code>return</code> <code>"index"</code><code>;</code>

①為user轉換比對查詢字元串參數的predicate

預設的綁定規則如下:

object在簡單屬性上如同eq

object在集合作為屬性如同contains

collection在簡單屬性上如同in

這些綁定可以通過@querydslpredicate的bindings屬性定制或者使用java8default methods給倉庫接口添加querydslbindercustomizer

<code>interface</code> <code>userreposotory </code><code>extends</code> <code>curdrepository&lt;user, string&gt;,</code>

<code>  </code><code>querydslpredicateexecutor&lt;user&gt;,  ①</code>

<code>  </code><code>querydslbindercustomizer&lt;quser&gt; {  ②</code>

<code>    </code><code>@override</code>

<code>    </code><code>default</code> <code>public</code> <code>void</code> <code>customize(querydslbindings bindings, quser user) {</code>

<code>      </code><code>bindings.bind(user.username).first((path, value) -&gt; path.contains(value));  ③</code>

<code>      </code><code>bindings.bind(string.</code><code>class</code><code>).first((stringpath path, string value) -&gt; path.containsignorecase(value));  ④</code>

<code>      </code><code>bindings.excluding(user.password);  ⑤</code>

<code>    </code><code>}</code>

① querydslpredicateexecutor為predicate提供特殊的查詢方法提供入口

② 在倉庫接口定義querydslbindercustomizer将自動注解@querydslpredicate(bindings=…)

③ 為username屬性定義綁定,綁定到一個簡單集合

④ 為string屬性定義預設綁定到一個不區分大小寫的集合

⑤ 從predicate移除密碼屬性

如果你使用spring jdbc子產品,你可能熟悉在datasource使用sql腳本來填充.一個類似的抽象在倉庫級别可以使用,盡管它不是使用sql作為資料定義語言,因為它必須由存儲決定.填充根據倉庫支援xml(通過spring的oxm抽象)和json(通過jackson)定義資料.

假設你有一個檔案data.json内容如下:

例36 json定義的資料

<code>[ { "_class" : "com.acme.person",</code>

<code>     </code><code>"firstname" : "dave",</code>

<code>      </code><code>"lastname" : "matthews" },</code>

<code>      </code><code>{ "_class" : "com.acme.person",</code>

<code>     </code><code>"firstname" : "carter",</code>

<code>      </code><code>"lastname" : "beauford" } ]</code>

你可以容易的根據spring data commons提供倉庫的命名空間填充元素填充你的倉庫.為了填充前面的資料到你的personrepository,像下面這樣配置:

例37 聲明一個jackson倉庫填充

<code>&lt;?</code><code>xml</code> <code>version</code><code>=</code><code>"1.0"</code> <code>encoding</code><code>=</code><code>"utf-8"</code><code>?&gt;</code>

<code>    </code><code>&lt;</code><code>repository:jackson2-populator</code> <code>locations</code><code>=</code><code>"classpath:data.json"</code> <code>/&gt;</code>

<code>  </code><code>&lt;/</code><code>beans</code><code>&gt;</code>

這樣的聲明可以讓data.json檔案可以被一個jackson的objectmpper讀取和反序列化.

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

要使用xml定義資料填充倉庫,你可以使用unmarshaller-populator元素.你配置它使用spring oxm提供給你的xml裝配選項.在spring reference documentation檢視更多細節.

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

<code>      </code><code>&lt;</code><code>repository:unmarshaller-populator</code> <code>locations</code><code>=</code><code>"classpath:data.json"</code>

<code>        </code><code>unmarshaller-ref</code><code>=</code><code>"unmarshaller"</code> <code>/&gt;</code>

<code>14</code>

<code>      </code><code>&lt;</code><code>oxm:jaxb2-marshaller</code> <code>contextpath</code><code>=</code><code>"com.acme"</code> <code>/&gt;</code>

<code>15</code>

<code>    </code><code>&lt;/</code><code>beans</code><code>&gt;</code>

spring mvc的實體類綁定

如果正在開發spring mvc web應用,你通常必須從url中解析實體類的id.預設的,你的任務是轉化請求參數或url參數到實體類并将它移交給下面或直接在實體上操作業務邏輯.這看起來像下面這樣:

<code>  </code><code>private</code> <code>final</code> <code>userrepository userrepository;</code>

<code>  </code><code>@autowired</code>

<code>  </code><code>public</code> <code>usercontroller(userrepository userrepository) {</code>

<code>    </code><code>assert.notnull(repository, </code><code>"repository must not be null!"</code><code>);</code>

<code>    </code><code>this</code><code>.userrepository = userrepository;</code>

<code>  </code><code>public</code> <code>string showuserform(</code><code>@pathvariable</code><code>(</code><code>"id"</code><code>) long id, model model) {</code>

<code>    </code><code>// do null check for id</code>

<code>    </code><code>user user = userrepository.findone(id);</code>

<code>16</code>

<code>    </code><code>// do null check for user</code>

<code>17</code>

<code>18</code>

<code>    </code><code>return</code> <code>"user"</code><code>;</code>

<code>19</code>

<code>20</code>

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

屬性編輯

spring3.0之前javapropertyeditors被使用.為了內建這些,spring data提出一個domainclasspropertyeditorregistrar來查詢所有注冊到applicatoncontext的spring data倉庫和一個定制的propertyeditor來管理實體類.

<code>&lt;</code><code>bean</code> <code>class</code><code>=</code><code>"….web.servlet.mvc.annotation.annotationmethodhandleradapter"</code><code>&gt;</code>

<code>  </code><code>&lt;</code><code>property</code> <code>name</code><code>=</code><code>"webbindinginitializer"</code><code>&gt;</code>

<code>    </code><code>&lt;</code><code>bean</code> <code>class</code><code>=</code><code>"….web.bind.support.configurablewebbindinginitializer"</code><code>&gt;</code>

<code>      </code><code>&lt;</code><code>property</code> <code>name</code><code>=</code><code>"propertyeditorregistrars"</code><code>&gt;</code>

<code>        </code><code>&lt;</code><code>bean</code> <code>class</code><code>=</code>

<code>          </code><code>"org.springframework.data.repository.support.domainclasspropertyeditorregistrar"</code><code>/&gt;</code>

<code>      </code><code>&lt;/</code><code>property</code><code>&gt;</code>

<code>    </code><code>&lt;/</code><code>bean</code><code>&gt;</code>

<code>  </code><code>&lt;/</code><code>property</code><code>&gt;</code>

<code>&lt;/</code><code>bean</code><code>&gt;</code>

如果你已經像上面這樣配置spring mvc,你可以向下面這樣配置你的控制器,進而減少不清晰和樣闆式的代碼

<a href="http://ifeve.com/spring-data-4-7/#viewsource">檢視源代碼</a>