前边使用DAO层以及Spring Data的神奇之处,都是基于我们在项目开始的时候选择了Spring Data,也就是pom.xml里的如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

这个依赖中包含的org.springframework.data就是Spring Data JPA。来继续看一下Spring Data的其他应用。

Spring Data REST

和之前相同的问题,如果我现在要为其他一个数据提供REST API,也要一个一个再复制一次所有的API代码吗?

Spring Data又站出来了,既然数据库操作封装好了,那么对应的REST API也可以按照这个思路来提供。

要使用Spring Data REST功能,需要添加一个依赖如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>

添加了这个东西之后,Spring Data REST会自动去扫描JPARepository的继承接口,Spring Data REST实际上需要下列的东西就可以正常工作:

  1. 作为Entity的Employee
  2. JPARepository的继承接口EmployeeSpringJPA
  3. Maven POM依赖中的spring-boot-starter-data-rest

配置好了这些之后,Spring Data REST会在后台自动生成对应的API地址。其逻辑如下:

方法 路径 操作
POST /employees 创建新对象
GET /employees 获取全部对象
GET /employees/{employeeId} 获取某个对象
PUT /employees/{employeeId} 修改某个对象
DELETE /employees/{employeeId} 删除某个对象

这些地址是符合HATEOAS标准的,所谓HATEOAS是REST服务构建的一种标准,Spring的文档中也对其进行了说明。HATEOAS使用HAL数据格式,具体可以再了解。

配置好了Maven依赖之后,无需进行任何改动,直接重启项目,然后访问localhost:8080

注意神奇的事情发生了,本来是没有配置任何根目录的对应文件,现在有了一个REST服务的返回结果:

{
    "_links": {
        "employees": {
            "href": "http://localhost:8080/employees{?page,size,sort}",
            "templated": true
        },
        "profile": {
            "href": "http://localhost:8080/profile"
        }
    }
}

这就是符合HATEOAS的RESF结果,展示了所有可用的链接。

来试验一下http://localhost:8080/employees

{
    "_embedded": {
        "employees": [
            {
                "firstName": "666666",
                "lastName": "Li",
                "email": "conyli@conyli.cc",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/employees/8"
                    },
                    "employee": {
                        "href": "http://localhost:8080/employees/8"
                    }
                }
            },
            {
                "firstName": "Jenny",
                "lastName": "Huang",
                "email": "jennyhuang@8roads.com",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/employees/12"
                    },
                    "employee": {
                        "href": "http://localhost:8080/employees/12"
                    }
                }
            },
            {
                "firstName": "Jenny",
                "lastName": "Huang",
                "email": "jennyhuang@8roads.com",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/employees/13"
                    },
                    "employee": {
                        "href": "http://localhost:8080/employees/13"
                    }
                }
            },
            {
                "firstName": "666666",
                "lastName": "Li",
                "email": "conyli@conyli.cc",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/employees/14"
                    },
                    "employee": {
                        "href": "http://localhost:8080/employees/14"
                    }
                }
            },
            {
                "firstName": "Jenny",
                "lastName": "Huang",
                "email": "jennyhuang@8roads.com",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/employees/15"
                    },
                    "employee": {
                        "href": "http://localhost:8080/employees/15"
                    }
                }
            }
        ]
    },
    "_links": {
        "self": {
            "href": "http://localhost:8080/employees{?page,size,sort}",
            "templated": true
        },
        "profile": {
            "href": "http://localhost:8080/profile/employees"
        }
    },
    "page": {
        "size": 20,
        "totalElements": 5,
        "totalPages": 1,
        "number": 0
    }
}

可以看到比我们之前的干巴巴的JSON要更复杂,但是更全面,带上了每个数据对象的访问地址。最下边还有符合HATEOAS的元数据,包括每页个数,总数据个数,和页数等元数据。

之前我们的路径是/api/***,现在Spring Data REST直接使用根目录,如果也想带一个前缀,可以在application.properties中添加一行:

spring.data.rest.base-path=/rest

这样所有的路径前边就带上了/rest

在Postman里试验一下增删改:

添加输入,通过Postman向/rest/employees发送一个POST请求,请求体就是一个普通的JSON:

{
    "firstName": "Jenny",
    "lastName": "Huang",
    "email": "jennyhuang@8roads.com"
}

可以看到返回了刚添加的数据以及对应的链接。

再试验PUT请求去修改,也是直接发送一个对应Employee类的JSON字符串,经过试验,传入的JSON数据中的id不重要,重要的是访问哪个链接,会修改实际访问的链接的对应的数据对象,而不是传出的JSON对象中的ID。

删除也是一样,只需要用DELETE请求访问对应地址即可。不过与我们自行编写的返回被删除对象的JSON值不同,这里删除不会返回任何东西,仅有一个Cookie和响应头,没有响应体。

Spring Data REST的其他配置

看完了Spring Data REST的简单运用,来看一下具体设置。

自定义路径

在之前我们自定义了REST服务的根路径。现在来看看链接中/employees这一段。默认情况下,Spring Data REST自动用Entity类名的小写加上s来作为这一段路径。

这带来一个问题就是并非所有的对象复数都是加s,所以也提供了自定义这一段路径的方法。

方法就是在定义DAO层接口的时候加上参数:

package cc.conyli.sbcrud.dao;

import cc.conyli.sbcrud.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;

@RepositoryRestResource(path = "members")
public interface EmployeeSpringJPA extends JpaRepository<Employee, Integer> {

}

这样配置之后,所有的访问路径就变成了/rest/members

分页

可以通过元数据看到,默认只返回20个元素,意味着默认的Page Size是20。如果想访问其后的页面怎么办。如果我们也是用程序去获取JSON的话,可以从元数据中取得page中的totalpages的值,然后以如下形式访问:

http://localhost:8080/rest/members?page=0
http://localhost:8080/rest/members?page=1
......

如果想要更改这些设置,也是在application.properties中:

spring.data.rest.default-page-size=15
spring.data.rest.max-page-size=30

从字面意思就可以看出配置的意义。其他的配置可以参考文档

排序

在之前的查询中很少提到过排序,然而在实际开发中,从数据库中取出的数据没有经过排序,是没有意义的。

Spring Data REST提供了方便的控制排序的方法,而不用去编写具体SQL语句,只需要添加一些请求参数即可:

http://localhost:8080/rest/members?sort=lastName
http://localhost:8080/rest/members?sort=email,desc
http://localhost:8080/rest/members?sort=firstName,lastName,desc

上边这些内容在浏览器中一试验就可以知道用法。将Page Size改成一个较小的数字之后,查询的时候还会自动返回额外的上下页链接:

{
  "_embedded" : {
    "employees" : [ {
      "firstName" : "666666",
      "lastName" : "Li",
      "email" : "conyli@conyli.cc",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/rest/members/8"
        },
        "employee" : {
          "href" : "http://localhost:8080/rest/members/8"
        }
      }
    }, {
      "firstName" : "Jenny",
      "lastName" : "Huang",
      "email" : "jennyhuang@8roads.com",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/rest/members/12"
        },
        "employee" : {
          "href" : "http://localhost:8080/rest/members/12"
        }
      }
    }, {
      "firstName" : "Jenny",
      "lastName" : "Huang",
      "email" : "jennyhuang@8roads.com",
      "_links" : {
        "self" : {
          "href" : "http://localhost:8080/rest/members/13"
        },
        "employee" : {
          "href" : "http://localhost:8080/rest/members/13"
        }
      }
    } ]
  },
  "_links" : {
    "first" : {
      "href" : "http://localhost:8080/rest/members?page=0&size=3"
    },
    "self" : {
      "href" : "http://localhost:8080/rest/members{?page,size,sort}",
      "templated" : true
    },
    "next" : {
      "href" : "http://localhost:8080/rest/members?page=1&size=3"
    },
    "last" : {
      "href" : "http://localhost:8080/rest/members?page=1&size=3"
    },
    "profile" : {
      "href" : "http://localhost:8080/rest/profile/members"
    }
  },
  "page" : {
    "size" : 3,
    "totalElements" : 5,
    "totalPages" : 2,
    "number" : 0
  }
}

所以只需要针对符合HATEOAS的JSON做好解析工作,用程序去访问REST API就非常方便了。