码迷,mamicode.com
首页 > 编程语言 > 详细

Spring Data Web 支持

时间:2020-09-08 20:59:55      阅读:47      评论:0      收藏:0      [点我收藏+]

标签:find   code   结果   继承   理解   能力   检查   应用程序开发   元素   

Spring Data Web 支持

点击左上角,关注:“锅外的大佬”

专注分享国外最新技术内容
帮助每位开发者更优秀地成长

1.概述

SpringMVC 和 SpringData 都用他们的方式在简化应用程序开发做的很棒。但是,如果我们将他们组合在一起呢?

在本教程中,我们将查看 SpringData的Web支持 以及它的解析器如何减少样板文件并使我们的 controller更有表现力。

在此过程中,我们将查看 Querydsl及其与 SpringData的集成。

2.背景

SpringData的 Web支持是一组在标准 SpringMVC平台上实现的与 Web相关的功能,目的是为 controller层添加额外的功能。

SpringDataweb支持的功能是围绕几个解析器类构建的。解析器简化了和 SpringDatarepository交互的 controller方法的实现,还使用额外的功能增强了他们。

这些功能包括从 repository层获取域对象,而不需要显式调用 repository实现,以及构建可以作为支持分页和排序的数据片段发送到客户端的 controller响应。

此外,对于带有一个或多个请求参数到 controller方法的请求可以在内部被解析为 Querydsl查询。

3.用Spring Boot项目演示

要了解我们如何使用 SpringDataweb支持来改进我们的 controller的功能,让我们创建一个基本的 SpringBoot项目。

我们的演示项目的 Maven依赖是相当标准的,我们稍后将讨论一些例外情况:

<dependency>

<groupId>
org.springframework.boot
</groupId>

<artifactId>
spring-boot-starter-data-jpa
</artifactId>
</dependency>
<dependency>

<groupId>
org.springframework.boot
</groupId>

<artifactId>
spring-boot-starter-web
</artifactId>
</dependency>
<dependency>

<groupId>
com.h2database
</groupId>

<artifactId>
h2
</artifactId>

<scope>
runtime
</scope>
</dependency>
<dependency>

<groupId>
org.springframework.boot
</groupId>

<artifactId>
spring-boot-starter-test
</artifactId>

<scope>
test
</scope>
</dependency>

在这种情况下,我们引入了 spring-boot-starter-web,我们将用他创建 RESTfulcontroller, spring-boot-starter-jpa用于实现持久层,以及 spring-boot-starter-test用于测试 controller API。

由于我们将采用 H2 作为底层数据库,我们也引入了 com.h2database。

让我们记住, spring-boot-starter-web 默认启用了 SpringDataweb支持。因此,我们不需要创建任何额外的 @Configuration类来使其在我们的应用程序中运行。

相反的,对于非 SpringBoot项目,我们需要定义一个 @Configuration 类并使用 @EnableWebMvc 和 @EnableSpringDataWebSupport 注解。

3.1.domain类

现在,让我们添加一个简单的 UserJPA 实体类到项目中,这样我们可以有一个可用的域模型:

@Entity
@Table
(
name 
=

"users"
)
public

class

User

{

@Id

@GeneratedValue
(
strategy 
=

GenerationType
.
AUTO
)

private

long
 id
;

private

final

String
 name
;

// standard constructor / getters / toString

}

3.2.repository层

为了简化代码,我们的演示 SpringBoot 应用程序的功能将缩小为只从 H2内存数据库中获取一些 User 实体。

SpringBoot可以轻松创建提供开箱即用的最小 CRUD功能的 repository 实现。因此,让我们定义一个和 UserJPA实体一起运行的简单的 repository 接口:

@Repository
public

interface

UserRepository

extends

PagingAndSortingRepository
<
User
,

Long
>

{}
除了继承 PagingAndSortingRepository 之外, UserRepository 接口的定义一点也不复杂。

这表示 SpringMVC启用了对数据库记录自动分页和排序功能。

3.3.controller层

现在,我们至少需要实现一个基础的 RESTfulcontroller,它充当客户端和 repository 层间的中间层。

因此,让我们创建一个 controller 类,它在其构造器中获取 UserRepository实例并添加一个方法来通过 id查找 User实体:

@RestController
public

class

UserController

{

@GetMapping
(
"/users/{id}"
)

public

User
 findUserById
(
@PathVariable
(
"id"
)

User
 user
)

{

return
 user
;

}
}

3.4.运行应用程序

最后,让我们定义应用程序的主类,并使用一些 User实体填充 H2数据库:

@SpringBootApplication
public

class

Application

{

public

static

void
 main
(
String
[]
 args
)

{

SpringApplication
.
run
(
Application
.
class
,
 args
);

}

@Bean

CommandLineRunner
 initialize
(
UserRepository
 userRepository
)

{

return
 args 
->

{

Stream
.
of
(
"John"
,

"Robert"
,

"Nataly"
,

"Helen"
,

"Mary"
).
forEach
(
name 
->

{

User
 user 
=

new

User
(
name
);
                userRepository
.
save
(
user
);

});
            userRepository
.
findAll
().
forEach
(
System
.
out
::
println
);

};

}
}

现在,让我们运行应用程序。和预期的一样,我们在启动时看到打印到控制台的持久化的 User 实体:

User
{
id
=
1
,
 name
=
John
}
User
{
id
=
2
,
 name
=
Robert
}
User
{
id
=
3
,
 name
=
Nataly
}
User
{
id
=
4
,
 name
=
Helen
}
User
{
id
=
5
,
 name
=
Mary
}

4.DomainClassConverter类

目前, UserController类只实现了 findUserById()方法。

初看之下,方法实现看起来相当简单。但它在幕后实际封装了许多 SpringDataweb支持的功能。由于该方法将 User实例作为参数,我们最终可能任务我们需要在请求中显示传递 domain对象,但我们没有。

SpringMVC 使用 DomainClassConverter 类转换 id路径变量到 domain类的 id类型并使用它从 repository层获取匹配的 domain对象,无需进一步查找。例如, http://localhost:8080/users/1 端点(endpoint)的 GET HTTP请求将返回如下结果:


{

"id"
:
1
,

"name"
:
"John"

}
因此,我们可以创建继承测试并检查 findUserById()方法的行为:

@Test
public

void
 whenGetRequestToUsersEndPointWithIdPathVariable_thenCorrectResponse
()

throws

Exception

{
    mockMvc
.
perform
(
MockMvcRequestBuilders
.
get
(
"/users/{id}"
,

"1"
)

.
contentType
(
MediaType
.
APPLICATION_JSON_UTF8
))

.
andExpect
(
MockMvcResultMatchers
.
status
().
isOk
())

.
andExpect
(
MockMvcResultMatchers
.
jsonPath
(
"$.id"
).
value
(
"1"
));

}
}

或者,我们可以使用 REST API 测试工具,例如 Postman,来测试该方法。 DomainClassConverter 的好处是我们不需要在 controller 方法中显式的调用 repository实现,通过简单的指定 id路径变量以及可解析的 domain类实例,我们已经触发了 domain对象的查找。

5.PageableHandlerMethodArgumentResolver类

SpringMVC支持在 controller 和 repository 中使用 Pageable类型。

简单来说, Pageable实例是一个保存分页信息的对象。因此,当我们传递 Pageable参数给 controller方法, SpringMVC使用 PageableHandlerMethodArgumentResolver类将 Pageable实例解析为 PageRequest对象,这是一个简单的 Pageable实现。

5.1.使用Pageable作为controller方法参数

要理解 PageableHandlerMethodArgumentResolver 类如何运行。让我们添加一个新方法到 UserController 类:


@GetMapping
(
"/users"
)
public

Page
<
User
>
 findAllUsers
(
Pageable
 pageable
)

{

return
 userRepository
.
findAll
(
pageable
);
}

和 findUserById() 方法作为对照,这里我们需要调用 repository实现来或者数据库中持久化的所有 UserJPA实体。由于该方法使用了 Pageable实例,因此它返回存储在 Page<User>对象中实体集的子集。

Page对象是我们可以用于检索有关分页结果信息的对象列表的子列表对象,包括结果总页数和我们正在检索的页数。

默认情况下, SpringMVC使用 PageableHandlerMethodArgumentResolver 类构造带有一下请求参数的 PageRequest对象:

  • page:我们要检索的页数——该参数从 0 开始且它的默认值为 0
  • size:我们要检索的页面的内容数量——默认值是 20
  • sort:我们可以使用一个或多个属性对结果排序,使用以下格式:property1,property2(,asc|desc) ——举个例子,?sort=name&sort=email,asc
    例如, http://localhost:8080/users端点(endpoint)的 GET 请求将返回一下输出:
{

"content"
:[

{

"id"
:
1
,

"name"
:
"John"

},

{

"id"
:
2
,

"name"
:
"Robert"

},

{

"id"
:
3
,

"name"
:
"Nataly"

},

{

"id"
:
4
,

"name"
:
"Helen"

},

{

"id"
:
5
,

"name"
:
"Mary"

}],

"pageable"
:{

"sort"
:{

"sorted"
:
false
,

"unsorted"
:
true
,

"empty"
:
true

},

"pageSize"
:
5
,

"pageNumber"
:
0
,

"offset"
:
0
,

"unpaged"
:
false
,

"paged"
:
true

},

"last"
:
true
,

"totalElements"
:
5
,

"totalPages"
:
1
,

"numberOfElements"
:
5
,

"first"
:
true
,

"size"
:
5
,

"number"
:
0
,

"sort"
:{

"sorted"
:
false
,

"unsorted"
:
true
,

"empty"
:
true

},

"empty"
:
false
}

我们可以看到,响应包括 first、 pageSize、 totalElements 和 totalPages JSON 元素。这非常有用,因为前端可以使用这些元素轻松创建分页机制。

另外,我们可以使用集成测试来检查 findAllUsers()方法:

@Test
public

void
 whenGetRequestToUsersEndPoint_thenCorrectResponse
()

throws

Exception

{
    mockMvc
.
perform
(
MockMvcRequestBuilders
.
get
(
"/users"
)

.
contentType
(
MediaType
.
APPLICATION_JSON_UTF8
))

.
andExpect
(
MockMvcResultMatchers
.
status
().
isOk
())

.
andExpect
(
MockMvcResultMatchers
.
jsonPath
(
"$[‘pageable‘][‘paged‘]"
).
value
(
"true"
));
}

5.2.自定义分页参数

在许多情况下,我们需要自定义分页参数。完成此操作的最简单方法是使用 @PageableDefault注解:

@GetMapping
(
"/users"
)
public

Page
<
User
>
 findAllUsers
(
@PageableDefault
(
value 
=

2
,
 page 
=

0
)

Pageable
 pageable
)

{

return
 userRepository
.
findAll
(
pageable
);
}

或者,我们可以使用 PageRequest 的 of() 静态工厂方法创建自定义 PageRequest对象并将其传递给 repository方法:

@GetMapping
(
"/users"
)
public

Page
<
User
>
 findAllUsers
()

{

Pageable
 pageable 
=

PageRequest
.
of
(
0
,

5
);

return
 userRepository
.
findAll
(
pageable
);
}

第一个参数是从 0 开始的页数,第二个是我们希望检索的页面大小。

在上面的例子中,我们创建了一个 User实体的 PageRequest对象,从第一页(0)开始,页面有 5 个实体。

另外,我们可以使用 page 和 size请求参数构建 PageRequest对象:

@GetMapping
(
"/users"
)
public

Page
<
User
>
 findAllUsers
(
@RequestParam
(
"page"
)

int
 page
,

@RequestParam
(
"size"
)

int
 size
,

Pageable
 pageable
)

{

return
 userRepository
.
findAll
(
pageable
);
}

使用此实现,http://localhost:8080/users?page=0&size=2 端点(endpoint)的 GET请求将返回 User对象的第一页,结果页的大小将为 2:

{

"content"
:

[

{

"id"
:

1
,

"name"
:

"John"

},

{

"id"
:

2
,

"name"
:

"Robert"

}

],

// continues with pageable metadata

}

6.SortHandlerMethodArgumentResolver类

分页是有效管理大量数据库记录的业界标准做法。但是,就其本身而言,如果我们不能以某种特定方式对记录进行排序那将毫无用处。

为此, SpringMVC提供了 SortHandlerMethodArgumentResolver类。该解析器自动从请求参数或 @SortDefault注解创建 Sort实例。

6.1.使用 sort controller 方法参数

为了弄清楚 SortHandlerMethodArgumentResolver类如何运作,让我们添加 findAllUsersSortedByName() 方法到 controller类:

@GetMapping
(
"/sortedusers"
)
public

Page
<
User
>
 findAllUsersSortedByName
(
@RequestParam
(
"sort"
)

String
 sort
,

Pageable
 pageable
)

{

return
 userRepository
.
findAll
(
pageable
);
}

在这种情况下, SortHandlerMethodArgumentResolver类将使用 sort请求参数创建 Sort对象。

因此,http://localhost:8080/sortedusers?sort=name端点(endpoint)的 GET 请求将返回一个 JSON 数组,按 name属性排序的 User对象列表:


{

"content"
:

[

{

"id"
:

4
,

"name"
:

"Helen"

},

{

"id"
:

1
,

"name"
:

"John"

},

{

"id"
:

5
,

"name"
:

"Mary"

},

{

"id"
:

3
,

"name"
:

"Nataly"

},

{

"id"
:

2
,

"name"
:

"Robert"

}

],

// continues with pageable metadata

}

6.2.使用 Sort.by() 静态工厂方法

或者,我们可以使用 Sort.by()静态工厂方法创建一个 Sort对象,该方法接受一个非 null,非空(empty)的用于排序的 String属性的数组。

在这种情况下,我们将只通过 name属性对记录进行排序:

@GetMapping
(
"/sortedusers"
)
public

Page
<
User
>
 findAllUsersSortedByName
()

{

Pageable
 pageable 
=

PageRequest
.
of
(
0
,

5
,

Sort
.
by
(
"name"
));

return
 userRepository
.
findAll
(
pageable
);
}

当然,我们可以使用多个属性,只要他们是在 domain类中声明了的。

6.3.使用@SortDefault注解

同样,我们可以使用 @SortDefault注解获得相同的结果:


@GetMapping
(
"/sortedusers"
)
public

Page
<
User
>
 findAllUsersSortedByName
(
@SortDefault
(
sort 
=

"name"
,

  direction 
=

Sort
.
Direction
.
ASC
)

Pageable
 pageable
)

{

return
 userRepository
.
findAll
(
pageable
);
}

最后,让我们创建集成测试检查方法的行为:

@Test
public

void
 whenGetRequestToSorteredUsersEndPoint_thenCorrectResponse
()

throws

Exception

{
    mockMvc
.
perform
(
MockMvcRequestBuilders
.
get
(
"/sortedusers"
)

.
contentType
(
MediaType
.
APPLICATION_JSON_UTF8
))

.
andExpect
(
MockMvcResultMatchers
.
status
().
isOk
())

.
andExpect
(
MockMvcResultMatchers
.
jsonPath
(
"$[‘sort‘][‘sorted‘]"
).
value
(
"true"
));
}

7. Querydsl Web支持

正如我们介绍中提到的, SpringDataweb支持允许我们在 controller方法中使用请求参数构建 Querydsl的 Predicate类型并构造 Querydsl查询。

为了简单起见,我们将看到 SpringMVC 如何将请求参数转换为 QuerydslBooleanExpression,然后传递给 QuerydslPredicateExecutor。

为此,我们首先需要添加 querydsl-apt 和 querydsl-jpaMaven 依赖到 pom.xml 文件:

<dependency>

<groupId>
com.querydsl
</groupId>

<artifactId>
querydsl-apt
</artifactId>
</dependency>
<dependency>

<groupId>
com.querydsl
</groupId>

<artifactId>
querydsl-jpa
</artifactId>
</dependency>

接下来,我们需要重构我们的 UserRepository 接口,该接口还必须继承 QuerydslPredicateExecutor 接口:

@Repository
public

interface

UserRepository

extends

PagingAndSortingRepository
<
User
,

Long
>,

QuerydslPredicateExecutor
<
User
>

{
}
最后,让我们将以下方法添加到 UserController类:

@GetMapping
(
"/filteredusers"
)
public

Iterable
<
User
>
 getUsersByQuerydslPredicate
(
@QuerydslPredicate
(
root 
=

User
.
class
)

Predicate
 predicate
)

{

return
 userRepository
.
findAll
(
predicate
);
}

尽管方法实现看起来非常简单,但它实际上暴露了许多表面之下的功能。

假设我们想要从数据库中获取匹配给定 name的所有 User实体。我们可以通过调用方法并在 URL中指定 name 请求参数来实现:

http://localhost:8080/filteredusers?name=John

正如预期,请求将返回以下结果:

[

{

"id"
:

1
,

"name"
:

"John"

}
]

和我们前面做的一样,我们可以使用集成测试检查 getUsersByQuerydslPredicate() 方法:

@Test
public

void
 whenGetRequestToFilteredUsersEndPoint_thenCorrectResponse
()

throws

Exception

{
    mockMvc
.
perform
(
MockMvcRequestBuilders
.
get
(
"/filteredusers"
)

.
param
(
"name"
,

"John"
)

.
contentType
(
MediaType
.
APPLICATION_JSON_UTF8
))

.
andExpect
(
MockMvcResultMatchers
.
status
().
isOk
())

.
andExpect
(
MockMvcResultMatchers
.
jsonPath
(
"$[0].name"
).
value
(
"John"
));
}

这只是 Querydslweb支持如何运行的一个基础示例。但它实际上没有暴露出它的所有能力。

现在,假设我们希望获取匹配给定 id的 User实体。在这种情况下,我们只需要在 URL中传递 id请求参数:

http://localhost:8080/filteredusers?id=2

在这种情况下,我们将得到这个结果:

[

{

"id"
:

2
,

"name"
:

"Robert"

}
]

很明显, Querydslweb支持是一个非常强大的功能,我们可以使用它来获取匹配给定条件的数据库记录。在所有情况下,整个过程归结为只调用具有不同请求参数的单个 controller 方法。

8.总结

在本教程中,我们深入查看了 Springweb支持的关键组件并学习了如何在演示 SpringBoot项目中使用它。

和往常一样,本教程中显示的所有示例都可以在 GitHub 上获得。

原文链接:https://www.baeldung.com/spring-data-web-support

作者:Alejandro Ugarte

译者:Darren Luo

推荐阅读:Spring Data JPA投影查询
上篇好文:Lombok 简介

Spring Data Web 支持

标签:find   code   结果   继承   理解   能力   检查   应用程序开发   元素   

原文地址:https://blog.51cto.com/14901350/2524991

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!