REST (Representational State Transfer)是一种Web软件架构风格,它是一种风格,而不是标准,匹配或兼容这种架构风格的网络服务称为REST服务。REST 服务简洁并且有层次,REST通常基于HTTP、URI和 XML 以及HTML这些现有的广泛流行的协议和标准。在REST中,资源是由URI来指定的,对资源的增删改查操作可以通过HTTP协议提供的GET、POST、PUT、DELETE等方法实现。使用REST可以更高效地利用缓存来提高响应速度,同时REST中的通信会话状态由客户端来维护,这可以让不同的服务器处理一系列请求中的不同请求,进而提高服务器的扩展性。在前后端分离项目中,一个设计良好的Web软件架构必然要满足REST风格。
在Spring MVC框架中,开发者可以通过@RestController注解开发一个RESTful服务,不过,Spring Boot对此提供了自动化配置方案,开发者只需要添加相关依赖就能快速构建一个RESTful服务。
在Spring Boot 中,使用Spring Data JPA和Spring Data Rest可以快速开发出一个RESTful应用。接下来向读者介绍 Spring Boot中非常方便的RESTful应用开发。
创建Spring Boot项目,添加如下依赖:
org.springframework.boot
spring-boot-starter-web
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-devtools
runtime
org.springframework.session
spring-session-data-redis
2.6.0
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-data-rest
com.alibaba
druid
1.2.9
这里的依赖除了数据库相关的依赖外,还有Spring Data JPA的依赖以及Spring Data Rest 的依赖。项目创建完成后,在application.yaml中配置基太的数据库连接信息。
spring:
datasource:
username: admin
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://120.55.61.170:3306/suohechuan?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
jpa:
show-sql: true
database: mysql
hibernate:
ddl-auto: update
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL57Dialect
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity(name="t_book")
public class Book{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String author;
}
public interface BookRepository extends JpaRepository<Book,Integer> {
}
创建BookRepository类继承JpaRepository,JpaRepository中默认提供了一些基本的操作方法,代码如下:
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
List<T> findAll();
List<T> findAll(Sort sort);
List<T> findAllById(Iterable<ID> ids);
<S extends T> List<S> saveAll(Iterable<S> entities);
void flush();
<S extends T> S saveAndFlush(S entity);
<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
...
由这段源码可以看到,基本的增删改查、分页查询方法 JpaRepository都提供了。
经过如上几步,一个RESTful服务就构建成功了,可能有读者会问“什么都没写呀!”,是的,这就是Spring Boot的魅力所在。
RESTful的测试首先需要有一个测试工具,可以直接使用浏览器中的插件,例如 Firefox中的RESTClient,或者直接使用Postman等工具,笔者这里是使用Postman测试的。
RESTful服务构建成功后,默认的请求路径是实体类名小写再加上后缀。
此时向数据库添加一条数据非常容易,发起一个post请求,请求地址为http://localhost:8080/books,如图所示。
当添加成功后,服务端会返回刚刚添加成功的数据的基本信息以及浏览地址。
查询是GET请求,分页查询请求路径为/books,请求URL如下:
http://localhost:8080/books
分页查询请求默认每页记录数是20条,页数为0(页码从0开始计),查询结果如下。
{
"_embedded": {
"books": [
{
"name": "三国演义",
"author": "罗贯中",
"_links": {
"self": {
"href": "http://localhost:8080/books/1"
},
"book": {
"href": "http://localhost:8080/books/1"
}
}
},
{
"name": "红楼梦",
"author": "曹雪芹",
"_links": {
"self": {
"href": "http://localhost:8080/books/2"
},
"book": {
"href": "http://localhost:8080/books/2"
}
}
},
{
"name": "西游记",
"author": "吴承恩",
"_links": {
"self": {
"href": "http://localhost:8080/books/3"
},
"book": {
"href": "http://localhost:8080/books/3"
}
}
},
{
"name": "水浒传",
"author": "施耐庵",
"_links": {
"self": {
"href": "http://localhost:8080/books/4"
},
"book": {
"href": "http://localhost:8080/books/4"
}
}
},
{
"name": "宋诗选注",
"author": "钱钟书",
"_links": {
"self": {
"href": "http://localhost:8080/books/5"
},
"book": {
"href": "http://localhost:8080/books/5"
}
}
},
{
"name": "朝花夕拾",
"author": "鲁迅",
"_links": {
"self": {
"href": "http://localhost:8080/books/6"
},
"book": {
"href": "http://localhost:8080/books/6"
}
}
},
{
"name": "故事新选",
"author": "鲁迅",
"_links": {
"self": {
"href": "http://localhost:8080/books/7"
},
"book": {
"href": "http://localhost:8080/books/7"
}
}
},
{
"name": "呐喊",
"author": "鲁迅",
"_links": {
"self": {
"href": "http://localhost:8080/books/8"
},
"book": {
"href": "http://localhost:8080/books/8"
}
}
},
{
"name": null,
"author": null,
"_links": {
"self": {
"href": "http://localhost:8080/books/9"
},
"book": {
"href": "http://localhost:8080/books/9"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/books"
},
"profile": {
"href": "http://localhost:8080/profile/books"
}
},
"page": {
"size": 20,
"totalElements": 9,
"totalPages": 1,
"number": 0
}
}
如果按照id查询,只需要在/books后面追加上 id即可(如图7-3所示),例如查询id为1的book,请求URL如下:(查询结果如下。)
http://localhost:8080/books/1
{
"name": "三国演义",
"author": "罗贯中",
"_links": {
"self": {
"href": "http://localhost:8080/books/1"
},
"book": {
"href": "http://localhost:8080/books/1"
}
}
}
在查询所有数据返回的结果中,除了所有图书的基本信息外,还有如何发起一个分页请求以及当前页面的分页信息。如果开发者想要修改请求页码和每页记录数,只需要在请求地址中携带上相关参数即可,如下请求表示查询第⒉页数据并且每页记录数为3:
http://localhost:8080/books?page=1&size=3
除了分页外,默认还支持排序,例如想查询第2页数据,每页记录数为3,并且按照id倒序排列,请求地址如下:(查询结果如下。)
http://localhost:8080/books?page=1&size=3&sort=id,desc
{
"_embedded": {
"books": [
{
"name": "朝花夕拾",
"author": "鲁迅",
"_links": {
"self": {
"href": "http://localhost:8080/books/6"
},
"book": {
"href": "http://localhost:8080/books/6"
}
}
},
{
"name": "宋诗选注",
"author": "钱钟书",
"_links": {
"self": {
"href": "http://localhost:8080/books/5"
},
"book": {
"href": "http://localhost:8080/books/5"
}
}
},
{
"name": "水浒传",
"author": "施耐庵",
"_links": {
"self": {
"href": "http://localhost:8080/books/4"
},
"book": {
"href": "http://localhost:8080/books/4"
}
}
}
]
},
"_links": {
"first": {
"href": "http://localhost:8080/books?page=0&size=3&sort=id,desc"
},
"prev": {
"href": "http://localhost:8080/books?page=0&size=3&sort=id,desc"
},
"self": {
"href": "http://localhost:8080/books?page=1&size=3&sort=id,desc"
},
"next": {
"href": "http://localhost:8080/books?page=2&size=3&sort=id,desc"
},
"last": {
"href": "http://localhost:8080/books?page=2&size=3&sort=id,desc"
},
"profile": {
"href": "http://localhost:8080/profile/books"
}
},
"page": {
"size": 3,
"totalElements": 9,
"totalPages": 3,
"number": 1
}
}
发送PUT请求可实现对数据的修改,对数据的修改是通过id进行的,因此请求路径中要有id,例如如下请求路径表示修改id为2的记录,具体的修改内容在请求体中,如图所示。
http://localhost:8080/books/2
PUT请求的返回结果就是被修改之后的记录。
发送DELETE 请求可以实现对数据的删除操作,例如删除id为1的记录,请求URL如下:
http://localhost:8080/books/1
DELETE请求没有返回值,上面这个请求发送成功后,id为1的记录就被删除了。
默认情况下,请求路径都是实体类名小写加 s,如果开发者想对请求路径进行重定义,通过@RepositoryRestResource注解即可实现,下面的案例只需在 BookRepository 上添加@RepositoryRestResource注解即可:
@RepositoryRestResource(path = "bbs", collectionResourceRel = "bs", itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
}
@RepositoryRestResource注解的 path 属性表示将所有请求路径中的books 都修改为bbs,如http://localhost:8080/bbs; collectionResourceRel属性表示将返回的JSON集合中 book 集合的key修改为bbs;itemResourceRel表示将返回的JSON集合中的单个book 的 key修改为b,如图所示。
默认的查询方法支持分页查询、排序查询以及按照 id查询,如果开发者想要按照某个属性查询,只需在BookRepository中定义相关方法并暴露出去即可,代码如下:
@RepositoryRestResource(path = "bbs", collectionResourceRel = "bs", itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
@RestResource(path = "author", rel = "author")
List<Book> findByAuthorContains(@Param("author") String author);
@RestResource(path = "name", rel = "name")
Book findByNameEquals(@Param("name ") String name);
}
代码解释:
默认情况下,凡是继承了Repository接口(或者Repository的子类)的类都会被暴露出来,即开发者可执行基本的增删改查方法。以上文的 BookRepository为例,如果开发者提供了BookRepository继承自Repository,就能执行对 Book 的基本操作,如果开发者继承了Repository但是又不想暴露相关操作,做如下配置即可:
@RepositoryRestResource(exported = false)
public interface BookRepository extends JpaRepository<Book, Integer> {
}
将@RepositoryRestResource注解中的exported属性置为false之后,则7.2.4小节中展示的增删改查接口都会失效,BookRepository类中定义的相关方法也会失效。若只是单纯地不想暴露某个方法,则在方法上进行配置即可,例如开发者想屏蔽DELETE接口,做如下配置即可:
@RepositoryRestResource(path = "bbs", collectionResourceRel = "bs", itemResourceRel = "b")
public interface BookRepository extends JpaRepository {
@RestResource(exported = false)
@Override
void deleteById(Integer integer);
}
@RestResource注解的exported属性默认为true,将之置为false即可。
之前的文章介绍了CORS两种不同的配置方式,一种是直接在方法上添加@CrossOrigin注解,另一种是全局配置。全局配置在这里依然适用,但是默认的RESTful 工程不需要开发者自己提供 Controller,因此添加在Controller的方法上的注解可以直接写在BookRepository 上,代码如下:
@CrossOrigin
@RepositoryRestResource(path = "bbs", collectionResourceRel = "bs", itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book, Integer> {
@RestResource(path = "author", rel = "author")
List<Book> findByAuthorContains(@Param("author") String author);
@RestResource(path = "name", rel = "name")
Book findByNameEquals(@Param("name ") String name);
}
此时,BookRepository 中的所有方法都支持跨域。如果只需要某一个方法支持跨域,那么将@CrossOrigin注解添加到某一个方法上即可。关于@CrossOrigin注解的详细用法可以参考以前Cors文章。
开发者也可以在application.properties中配置一些常用属性,代码如下:
#每页默认记录数,默认值为20
spring.data.rest.default-page-size=2
#分页查询页码参数名,默认值为page
spring.data.rest.page-param-name=page
#分页查询记录数参数名,默认值为size
spring.data.rest.limit-param-name=size
#分页查询排序参数名,默认值为sort
spring.data.rest.sort-param-name=sort
#base-path表示给所有请求路径都加上前缀
spring.data.rest.base-path=/api
#添加成功时是否返回添加内容
spring.data.rest.return-body-on-create=true
#更新成功时是否返回更新内容
spring.data.rest.return-body-on-update=true
当然,这些XML配置也可以在 Java代码中配置,且代码中配置的优先级高于application.properties 配置的优先级,代码如下:
@Configuration
public class RestConfig implements RepositoryRestConfigurer {
@Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) {
config.setDefaultPageSize(2)
.setPageParamName("page")
.setBasePath("/api")
.setSortParamName("sort")
.setReturnBodyOnCreate(true)
.setReturnBodyOnUpdate(true);
}
}
这里每项代码配置的含义都和 application.properties中的配置一一对应,因此不再赘述。
在之前的文章中向读者介绍了MongoDB整合Spring Boot,而使用Spring Boot快速构建 RESTful服务除了结合Spring Data JPA之外,也可以结合Spring Data MongoDB 实现。使用Spring DataMongoDB构建RESTful服务也是三个步骤,分别如下。
首先创建Spring Boot Web项目,添加如下依赖:
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-mongodbartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-restartifactId>
dependency>
这里Spring Data Rest的依赖和7.2节中的一致,只是将Spring Data JPA的依赖变为Spring DataMongoDB 的依赖。项目创建成功后,在application.properties 中配置MongoDB的基本连接信息,代码如下:
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.database=test
spring.data.mongodb.host=170.0.0.1
spring.data.mongodb.port=27017
spring.data.mongodb.username=suohechuan
spring.data.mongodb.password=123@456
这段配置的含义可以参考6.2.3节,这里不再赘述。
接下来创建一个普通的Book 实体类,代码如下:
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity(name="t_book")
public class Book{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String author;
}
创建BookRepository 实现对Book的基本操作:
public interface BookRepository extends MongoRepository<Book, Integer> {
}
如此之后,一个 RESTful服务就搭建成功了。在启动Spring Boot 项目之前,记得要先启动MongoDB。Spring Boot项目启动成功后,接下来的测试环节与JPA小节的第5~8步一致。另外,JPA小节介绍的Spring Data Rest配置在这里一样适用,因此不再赘述。
本章向读者介绍了Spring Boot构建RESTful服务,结合Spring Data Rest、Spring Data JPA 以及Spring Data MongoDB,Spring Boot可以快速构建出一个基本的RESTful服务,而开发者可以结合具体情况选择关系型数据库或者非关系型数据库作为数据支撑。在一些常规功能的项目中,Spring Boot的这些特性可以帮助开发者省去许多繁杂臃肿的配置。