Spring Data REST
1.REST(Representational State Transfer)
用来规范应用如何在 HTTP 层与 API 提供方进行数据交互
REST约束
1.客户端-服务器结构 2.无状态 3.可缓存 4.分层的系统 5.按需代码(可选) 6.统一接口。 该约束是 REST 服务的基础,是客户端和服务器之间的桥梁。该约束又包含下面4个子约束: 资源标识符。每个资源都有各自的标识符。客户端在请求时需要指定该标识符。在 REST 服务中,该标识符通常是 URI。客户端所获取的是资源的表达(representation),通常使用 XML 或 JSON 格式。 通过资源的表达来操纵资源。客户端根据所得到的资源的表达中包含的信息来了解如何操纵资源,比如对资源进行修改或删除。 自描述的消息。每条消息都包含足够的信息来描述如何处理该消息。 超媒体作为应用状态的引擎(HATEOAS)。客户端通过服务器提供的超媒体内容中动态提供的动作来进行状态转换。
2.HATEOAS(The Hypermedia As The Engine Of Application Statue)
是REST架构的主要约束
REST成熟的模型
第一个层次(Level 0)的 Web 服务只是使用 HTTP 作为传输方式,实际上只是远程方法调用(RPC)的一种具体形式。SOAP 和 XML-RPC 都属于此类。 第二个层次(Level 1)的 Web 服务引入了资源的概念。每个资源有对应的标识符和表达。 第三个层次(Level 2)的 Web 服务使用不同的 HTTP 方法来进行不同的操作,并且使用 HTTP 状态码来表示不同的结果。如 HTTP GET 方法来获取资源,HTTP DELETE 方法来删除资源。 第四个层次(Level 3)的 Web 服务使用 HATEOAS。在资源的表达中包含了链接信息。客户端可以根据链接来发现可以执行的动作。
根据REST 成熟度模型中可以看到,使用 HATEOAS 的 REST 服务是成熟度最高的,也是推荐的做法
RESTful API最好做到Hypermedia,或HATEOAS,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么
eg:
{ "links": { "self": { "href": "http://api.com/items" }, "item": [ { "href": "http://api.com/items/1" }, { "href": "http://api.com/items/2" } ] "data": [ {"itemName":"a"}, {"itemName":"b"} ] }
3.HAL(Hypertext Application Language)
HAL是一种简单的格式,为 API 中的资源提供简单一致的链接
HAL可以用来实现HATEOAS
HAL 模型包括:
链接
内嵌资源
状态
HAL专为构建API而设计,在这些API中,客户端通过以下链接在客户端中浏览资源
4.spring-boot-starter-data-rest使用Spring Boot构建RESTful API
Spring Data REST是基于Spring Data的repository之上,可以把 repository 自动输出为REST资源
Spring Data REST把我们需要编写的大量REST模版接口做了自动化实现,并符合HAL的规范
(1)添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency>
(2)示例
实体类User
package com.example.demo.model; import lombok.*; import org.hibernate.annotations.CreationTimestamp; import javax.persistence.*; import java.util.Date; @Entity @Table(name = "users") @Data @Builder @ToString(callSuper = true) @NoArgsConstructor @AllArgsConstructor public class Users { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String name; @Column(updatable = false) @CreationTimestamp private Date createTime; }
UsersRepository
package com.example.demo.repository; import com.example.demo.model.Users; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface UsersRepository extends JpaRepository<Users,Integer> { }
这样就已经提供了一个关于user的rest api,简单的增、删、改、查都有了。不用写Controller,spring已经实现了
启动项目
GET http://127.0.0.1:8080
{ "_links": { "userses": { "href": "http://127.0.0.1:8080/userses{?page,size,sort}", "templated": true }, "profile": { "href": "http://127.0.0.1:8080/profile" } } }
分页+排序查询
GET http://127.0.0.1:8080/userses?page=1&size=2&sort=createTime
{ "_embedded": { "userses": [ { "name": "Nana", "createTime": "2020-04-07T02:10:12.469+0000", "_links": { "self": { "href": "http://127.0.0.1:8080/userses/5" }, "users": { "href": "http://127.0.0.1:8080/userses/5" } } }, { "name": "xyz", "createTime": "2020-04-07T02:10:12.469+0000", "_links": { "self": { "href": "http://127.0.0.1:8080/userses/3" }, "users": { "href": "http://127.0.0.1:8080/userses/3" } } } ] }, "_links": { "first": { "href": "http://127.0.0.1:8080/userses?page=0&size=2&sort=createTime,asc" }, "prev": { "href": "http://127.0.0.1:8080/userses?page=0&size=2&sort=createTime,asc" }, "self": { "href": "http://127.0.0.1:8080/userses" }, "next": { "href": "http://127.0.0.1:8080/userses?page=2&size=2&sort=createTime,asc" }, "last": { "href": "http://127.0.0.1:8080/userses?page=2&size=2&sort=createTime,asc" }, "profile": { "href": "http://127.0.0.1:8080/profile/userses" } }, "page": { "size": 2, "totalElements": 5, "totalPages": 3, "number": 1 } }
查询某一个
GET http://127.0.0.1:8080/userses/5
{ "name": "Nana", "createTime": "2020-04-07T02:10:12.469+0000", "_links": { "self": { "href": "http://127.0.0.1:8080/userses/5" }, "users": { "href": "http://127.0.0.1:8080/userses/5" } } }
新增
POST http://127.0.0.1:8080/userses
修改
PUT http://127.0.0.1:8080/userses/481
删除
DELETE http://127.0.0.1:8080/userses/481
(3)其他
注解 @RepositoryRestResource指定切入点
eg:
映射到 /user上
添加自定义查询
findByName
findByNameContaining
package com.example.demo.repository; import com.example.demo.model.Users; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.stereotype.Repository; import java.util.List; @RepositoryRestResource(collectionResourceRel = "user", path = "user") public interface UsersRepository extends JpaRepository<Users,Integer> { List<Users> findByName(@Param("name") String name); List<Users> findByNameContaining(@Param("name") String name); }
注:
方法的定义,参数要有 @Param 注解
重新启动项目
GET http://127.0.0.1:8080/user
{ "_embedded": { "user": [ { "name": "Lili", "createTime": "2020-04-07T02:50:27.501+0000", "_links": { "self": { "href": "http://127.0.0.1:8080/user/1" }, "users": { "href": "http://127.0.0.1:8080/user/1" } } }, { "name": "Fiona", "createTime": "2020-04-07T02:50:27.508+0000", "_links": { "self": { "href": "http://127.0.0.1:8080/user/2" }, "users": { "href": "http://127.0.0.1:8080/user/2" } } }, { "name": "xyz", "createTime": "2020-04-07T02:50:27.508+0000", "_links": { "self": { "href": "http://127.0.0.1:8080/user/3" }, "users": { "href": "http://127.0.0.1:8080/user/3" } } } } ] }, "_links": { "self": { "href": "http://127.0.0.1:8080/user{?page,size,sort}", "templated": true }, "profile": { "href": "http://127.0.0.1:8080/profile/user" }, "search": { "href": "http://127.0.0.1:8080/user/search" } }, "page": { "size": 20, "totalElements": 5, "totalPages": 1, "number": 0 } }
GET http://127.0.0.1:8080/user/search
{ "_links": { "findByNameContaining": { "href": "http://127.0.0.1:8080/user/search/findByNameContaining{?name}", "templated": true }, "findByName": { "href": "http://127.0.0.1:8080/user/search/findByName{?name}", "templated": true }, "self": { "href": "http://127.0.0.1:8080/user/search" } } }
查询 name=xyz
GET http://127.0.0.1:8080/user/search/findByName?name=xyz
{ "_embedded": { "user": [ { "name": "xyz", "createTime": "2020-04-07T03:14:11.921+0000", "_links": { "self": { "href": "http://127.0.0.1:8080/user/3" }, "users": { "href": "http://127.0.0.1:8080/user/3" } } } ] }, "_links": { "self": { "href": "http://127.0.0.1:8080/user/search/findByName?name=xyz" } } }
查询 name 包含i的
GET http://127.0.0.1:8080/user/search/findByNameContaining?name=i
{ "_embedded": { "user": [ { "name": "Lili", "createTime": "2020-04-07T03:14:11.913+0000", "_links": { "self": { "href": "http://127.0.0.1:8080/user/1" }, "users": { "href": "http://127.0.0.1:8080/user/1" } } }, { "name": "Fiona", "createTime": "2020-04-07T03:14:11.920+0000", "_links": { "self": { "href": "http://127.0.0.1:8080/user/2" }, "users": { "href": "http://127.0.0.1:8080/user/2" } } } ] }, "_links": { "self": { "href": "http://127.0.0.1:8080/user/search/findByNameContaining?name=i" } } }
配置
在application.properties 中配置
给所有的接口添加统一的前缀
spring.data.rest.base-path=/rest
添加/更新成功时是否返回添加/更新记录
spring.data.rest.return-body-on-create=true spring.data.rest.return-body-on-update=true
注:
如果为false,则返回空白