天天看点

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>