Skip to content

30SpringDataRet是什么?和JPA是什么关系?

通过之前课时的内容,相信你已经对 JPA 有了深入的认识了,那么 JPA 还有哪些应用场景呢?这一讲,我们将通过 Spring Data Rest 来聊聊实体和 Respository 的另外一种用法。

首先通过一个 Demo 让你感受一下,怎么快速创建一个 Rest 风格的 Server 服务端。

Spring Data Rest Demo

我们通过以下四个步骤演示一下 Spring Data Rest 的效果。

第一步:通过 gradle 引入相关的 jar 依赖,代码如下所示。

java
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// spring data rest的依赖,由于我们使用的是spring boot,所以只需要添加starter即可
implementation("org.springframework.boot:spring-boot-starter-data-rest")
//我们添加swagger方便看得出来,生成了哪些api接口
implementation 'io.springfox:springfox-boot-starter:3.0.0'
// swagger 对spring data reset支持需要添加 springfox-data-rest
implementation 'io.springfox:springfox-data-rest:3.0.0'

添加完依赖之后,我们可以通过 gradle 的依赖视图看一下都用了哪些 jar 包。

通过上图可以很清晰地看到 spring-data-rest 的 jar 包引入情况,以及我们依赖的 spring-data-jpa 和 Swagger。

第二步:在项目里面添加 SpringFoxConfiguration 开启 Swagger,代码如下所示。

java
@Configuration
@EnableSwagger2
public class SpringFoxConfiguration {}

第三步:通过 application.properties 指定一个 base-path,以方便和我们自己的 api 进行区分,代码如下所示。

java
# 我们可以通过spring data rest里面提供的配置项,指定bast-path
spring.data.rest.base-path=api/rest/v2

第四步:直接启动项目,就可以看到效果了,不需要任何额外的 controller 的配置和设置

启动成功之后,我们就会发现,里面多了很多 api/rest/v2 等 Rest 风格的 API,并且可以直接使用。如下图所示,不只有我们自己的 Controller,还有 Spring DataRest 自己生成的 API。

这时我们打开 Swagger 看一下:http://127.0.0.1:8087/swagger-ui/

由于我们的 Demo 的项目结构是下图所示这样的。

你会发现有几个 Repository 会帮我们生成几个对应的 Rest 协议的 API,除了基本的 CRUD,例如 UserInfoRespository 自定义的方法它们也会帮我们展示出来。而 Room 实体我们没有对应的 Repository,所以不会有对应的 Rest 风格 API 生成。

通过这个 Demo 你可以想象一下,如果要做一个 Rest 风格的 Server API 项目,是不是只需要把对应的 Entity 和 Repository 建好,就可以直接拥有了所有的 CRUD 的 API 了?这样可以大大提高我们的开发效率。

下面我们详细看一下 Spring Data Rest 的基本用法。

Spring Data Rest 基本用法

通过 Demo 可以看得出来,Spring Data Rest 的核心功能就是把 Spring Data Resositories 里对外暴露的方法生成对应的 API,如我们上面的 AddressRepository,里面对应的实体是 Address,代码如下。

java
public interface AddressRepository extends JpaRepository<Address, Long>{
}

它帮我们生成的 API 有下图所示的这些。

从 swagger 我们可以看到 Spring Data Rest 的几点用法。

语义化的方法

把实体转化成复数的形式,生成基本的 PATCH、GET、PUT、POST、DELETE 带有语义的 Rest 相应的方法,包括的子资源有如下几个。

  • GET:返回单个实体

  • PUT:更新资源

  • PATCH:与 PUT 类似,但部分是更新资源状态

  • DELETE:删除暴露的资源

  • POST:从给定的请求正文创建一个新的实体

默认的状态码的支持

  • 200 OK:适用于纯粹的 GET 请求

  • 201 Created:针对创建新资源的 POST 和 PUT 请求

  • 204 No Content:对于 PUT、PATCH 和 DELETE 请求

  • 401 没有认证

  • 403 没有权限,拒绝访问

  • 404 没有找到对应的资源

分页支持

通过 Swagger,我们可以看到其完全对分页和排序进行支持,完全兼容我们之前讲过的 Spring Data JPA 的分页和排序的参数,如下图所示。

通过 @RepositoryRestResource 改变资源的 metaData

代码如下所示。

java
@RepositoryRestResource(
      exported = true, //资源是否暴露,默认true
      path = "users",//资源暴露的path访问路径,默认实体名字+s
      collectionResourceRel = "userInfo",//资源名字,默认实体名字
      collectionResourceDescription = @Description("用户基本信息资源"),//资源描述
      itemResourceRel = "userDetail",//取资源详情的Item名字
      itemResourceDescription = @Description("用户详情")
)

我们将其放置在 UserInfoRepository 上面测试一下,代码变更如下。

java
@RepositoryRestResource(
      exported = true,
      path = "users",
      collectionResourceRel = "userInfo",
      collectionResourceDescription = @Description("用户资源"),
      itemResourceRel = "userDetail",
      itemResourceDescription = @Description("用户详情")
)
public interface UserInfoRepository extends JpaRepository<UserInfo, Long> {}

这时通过 Swagger 可以看到,url 的 path 上面变成了 users,而 body 里面的资源名字变成了 userInfo,取 itemResource 的 URL 描述变成了 userDetail,如下图所示。

@RepositoryRestResource 是使用在 Repository 类上面的全局设置,我们也可以针对具体的 Repsitory 里面的每个方法进行单独设置,这就是另外一个注解:@RestResource。

@RestResource 改变 rest 的 SearchPath

代码如下所示。

java
@RestResource(
      exported = true,//是否暴露给Search
      path = "findCities",//Search后面的path路径
      rel = "cities"//资源名字
)

可以将其用于 ***Repository 的方法中和 @Entity 的实体关系上,那么我们在 address 的 findByAddress 方法上面做一个测试,看看会变成什么样,代码如下所示。

java
public interface AddressRepository extends JpaRepository<Address, Long>{
    @RestResource(
            exported = true,//是否暴露给Search
            path = "findCities",//Search后面的path路径
            rel = "cities"//资源名字
    )
    Page<Address> findByAddress(@Param("address") String address, Pageable pageable);
}

我们打开 Swagger 看一下结果,会发现 search 后面的 path 路径被自定义了,如下图所示。

同时这个注解也可以配置在关联关系上,如 @OneToMany 等。如果我们不想某些方法暴露成 RestAPI,就直接添加 @RestResource(exported = false) 这一注解即可,例如一些删除方法等。

spring data rest 的配置项支持

这个可以直接在 application.properties 里面配置,我们在 IDEA 里面输入前缀的时候,就会有如下提示。

对应的描述如下表所示。

Spring Data Rest 的常见用法我们介绍完了,之前还讲过 Spring Data JPA 对 Jackson 的支持,它在 Spring Data Rest 里面完全适用,下面来看一下。

返回结果对 Jackson 的支持

通过 jackson 的注解,可以改变 rest api 的属性的名字,或者忽略具体的某个属性。我们在 address 的实体里面,改变一下属性 city 的名字,同时忽略 address 属性,代码会变成如下所示的样子。

java
@Entity
@Table
@Data
@SuperBuilder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = "userInfo")
public class Address extends BaseEntity {
   @JsonProperty("myCity") //改变JSON响应的属性名字
   private String city;
   @JsonIgnore //JSON解析的时候忽略某个属性
   private String address;
}

我们通过 Swagger 里面的 Description 可以看到,当前的资源的描述发生了变化,字段名变成了 myCity,address 属性没有了,具体如下图所示。

Spring Data Rest 返回 ResponseBody 的原理和接收 RequestBody 的原理都是基于 JSON 格式的,我们之前讲的 Jackson 的所有注解语法同样适用。

那么介绍了这么多,到底 Spring Data Rest 和 Spring Data JPA 是什么关系呢?我们来总结一下。

Spring Data Rest 和 Spring Data JPA 的关系

大概有如下几点。

  1. Spring Data JPA 基于 JPA 协议提供了一套标准的 Repository 的操作统一接口,方法名和 @Query 都是有固定语法和约定的规则的。

  2. Spring Data Rest 利用 JPA 的约定和语法,利用 Java 反射、动态代理等机制,很容易可以生成一套标准的 rest 风格的 API 协议操作。

  3. 也就是说 JPA 制定协议和标准,Spring Data Rest 基于这套协议生成 rest 风格的 Controller。

总结

由于篇幅有限,SpringDataRest 本身的原理和实现方式一个课时是介绍不完的,虽然这一讲的内容不多,但其精髓都在这里了。

我想表达的重点是 JPA 的应用领域其实有很多,我的讲解就是想帮你打开思路,在写一些基于实体的框架时就可以参考 Spring Data Rest 的做法。例如yahoo 团队设计的 JSONAPI 协议,以及Elide 的实现,也是基于 JPA 的实体注解来实现的。

甚至 Spring 在研究的 graph QL,也可以基于约定的实体来做很多事情。所以当你把 JPA "玩得很溜"的时候,就可以大大提升开发效率。

最后欢迎你在留言区发表自己的看法,希望我们可以一起讨论。下一讲我们来聊聊如何通过 spring boot test 提高开发效率,到时见。

点击下方链接查看源码(不定时更新)
https://github.com/zhangzhenhuajack/spring-boot-guide/tree/master/spring-data/spring-data-jpa