1. 概述
LinkRest是一个用于构建数据驱动的REST Web服务的开源框架,它建立在JAX-RS和Apache Cayenne ORM之上,并使用基于HTTP/JSON的消息协议。
基本上,这个框架旨在提供一种在Web上展示我们的数据存储的简单方法。
在下面的部分中,我们将了解如何使用LinkRest构建REST Web服务来访问数据模型。
2. Maven依赖
要开始使用该库,首先我们需要添加link-rest依赖:
<dependency>
<groupId>com.nhl.link.rest</groupId>
<artifactId>link-rest</artifactId>
<version>2.9</version>
</dependency>
这也带来了cayenne-server工件。
此外,我们将使用Jersey作为JAX-RS实现,因此我们需要添加jersey-container-servlet依赖,以及jersey-media-moxy用于序列化JSON响应:
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.25.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.25.1</version>
</dependency>
对于我们的示例,我们将使用内存中的H2数据库,因为它更容易设置;因此,我们还将添加h2:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.196</version>
</dependency>
3. Cayenne数据模型
我们将要使用的数据模型包含一个Department和一个Employee实体,它们代表一对多的关系:
如上所述,LinkRest与使用Apache Cayenne ORM生成的数据对象配合使用。使用Cayenne不是本文的主题,因此有关更多信息,请查看Apache Cayenne文档。
我们将把Cayenne项目保存在cayenne-linkrest-project.xml文件中。
运行cayenne-maven-plugin后,将生成两个_Department和_Employee抽象类-它们将扩展CayenneDataObject类,以及从它们派生的两个具体类,Department和Employee。
后者是我们可以定制并与LinkRest一起使用的类。
4. LinkRest应用程序启动
在下一部分中,我们将编写和测试REST端点,因此为了能够运行它们,我们需要设置我们的运行时。
由于我们使用Jersey作为JAX-RS实现,因此让我们添加一个扩展ResourceConfig的类并指定包含定义REST端点的类的包:
@ApplicationPath("/linkrest")
public class LinkRestApplication extends ResourceConfig {
public LinkRestApplication() {
packages("cn.tuyucheng.taketoday.linkrest.apis");
// load linkrest runtime
}
}
在同一个构造函数中,我们需要构建LinkRestRuntime并将其注册到Jersey容器,此类基于首先加载CayenneRuntime:
ServerRuntime cayenneRuntime = ServerRuntime.builder()
.addConfig("cayenne-linkrest-project.xml")
.build();
LinkRestRuntime lrRuntime = LinkRestBuilder.build(cayenneRuntime);
super.register(lrRuntime);
最后,我们需要将该类添加到web.xml中:
<servlet>
<servlet-name>linkrest</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>cn.tuyucheng.taketoday.LinkRestApplication</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>linkrest</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
5. REST资源
现在我们已经有了模型类,我们可以开始编写REST资源。
REST端点是使用标准JAX-RS注解创建的,而响应是使用LinkRest类构建的。
我们的示例将包括编写一系列使用不同HTTP方法访问/department URL的CRUD端点。
首先,让我们创建DepartmentResource类,它映射到/department:
@Path("department")
@Produces(MediaType.APPLICATION_JSON)
public class DepartmentResource {
@Context
private Configuration config;
// ...
}
LinkRest类需要JAX-RS Configuration类的实例,该实例使用Context注解注入,也是由JAX-RS提供的。
接下来,让我们继续编写访问Department对象的每个端点。
5.1 使用POST创建实体
为了创建实体,LinkRest类提供了create()方法,该方法返回一个UpdateBuilder对象:
@POST
public SimpleResponse create(String data) {
return LinkRest.create(Department.class, config).sync(data);
}
data参数可以是表示Department的单个JSON对象,也可以是对象数组。此参数使用sync()方法发送到UpdateBuilder,以创建一个或多个对象并将记录插入数据库,之后该方法返回一个SimpleResponse。
该库为响应定义了另外3种格式:
- DataResponse<T>:表示T集合的响应
- MetadataResponse<T>:包含有关类型的元数据信息
- SimpleResponse:包含两个success和message属性的对象
接下来,让我们使用curl向数据库添加Department记录:
curl -i -X POST -H "Content-Type:application/json"
-d "{"name":"IT"}" http://localhost:8080/linkrest/department
因此,该命令返回状态201 Created和success属性:
{"success":true}
我们还可以通过发送JSON数组来创建多个对象:
curl -i -X POST -H "Content-Type:application/json"
-d "[{"name":"HR"},{"name":"Marketing"}]"
http://localhost:8080/linkrest/department
5.2 使用GET读取实体
查询对象的主要方法是LinkRest类中的select()方法,该方法返回一个SelectBuilder对象,我们可以使用该对象链接其他查询或过滤方法。
让我们在DepartmentResource类中创建一个端点,返回数据库中的所有Department对象:
@GET
public DataResponse<Department> getAll(@Context UriInfo uriInfo) {
return LinkRest.select(Department.class, config).uri(uriInfo).get();
}
uri()调用设置了SelectBuilder的请求信息,而get()返回了包装为DataResponse<Department>对象的Departments集合。
让我们使用此端点获取之前添加的部门:
curl -i -X GET http://localhost:8080/linkrest/department
响应采用带有data数组和total属性的JSON对象的形式:
{"data":[
{"id":200,"name":"IT"},
{"id":201,"name":"Marketing"},
{"id":202,"name":"HR"}
],
"total":3}
或者,要检索对象集合,我们也可以使用getOne()而不是get()返回单个对象。
让我们添加一个映射到/department/{departmentId}的端点,该端点返回具有给定id的对象。为此,我们将使用byId()方法过滤记录:
@GET
@Path("{id}")
public DataResponse<Department> getOne(@PathParam("id") int id, @Context UriInfo uriInfo) {
return LinkRest.select(Department.class, config)
.byId(id).uri(uriInfo).getOne();
}
然后,我们可以向此URL发送GET请求:
curl -i -X GET http://localhost:8080/linkrest/department/200
结果是一个包含一个元素的data数组:
{"data":[{"id":200,"name":"IT"}],"total":1}
5.3 使用PUT更新实体
要更新记录,我们可以使用update()或createOrUpdate()方法。后者将更新记录(如果记录存在),如果记录不存在则创建记录:
@PUT
public SimpleResponse createOrUpdate(String data) {
return LinkRest.createOrUpdate(Department.class, config).sync(data);
}
与前面的部分类似,data参数可以是单个对象或对象数组。
让我们更新之前添加的一个部门:
curl -i -X PUT -H "Content-Type:application/json"
-d "{"id":202,"name":"Human Resources"}"
http://localhost:8080/linkrest/department
这将返回一个包含成功或错误消息的JSON对象;之后,我们可以验证ID为202的部门名称是否已更改:
curl -i -X GET http://localhost:8080/linkrest/department/202
因此,这个命令返回了具有新名称的对象:
{"data":[
{"id":202,"name":"Human Resources"}
],
"total":1}
5.4 使用DELETE删除实体
并且,要删除一个对象,我们可以调用delete()方法创建一个DeleteBuilder,然后使用id()方法指定我们要删除的对象的主键:
@DELETE
@Path("{id}")
public SimpleResponse delete(@PathParam("id") int id) {
return LinkRest.delete(Department.class, config).id(id).delete();
}
然后我们可以使用curl调用该端点:
curl -i -X DELETE http://localhost:8080/linkrest/department/202
5.5 处理实体之间的关系
LinkRest还包含使处理对象之间关系更容易的方法。
由于Department与Employee具有一对多关系,因此让我们添加一个访问EmployeeSubResource类的/department/{departmentId}/employees端点:
@Path("{id}/employees")
public EmployeeSubResource getEmployees(@PathParam("id") int id, @Context UriInfo uriInfo) {
return new EmployeeSubResource(id);
}
EmployeeSubResource类对应于一个部门,因此它有一个设置部门ID的构造函数,以及Configuration实例:
@Produces(MediaType.APPLICATION_JSON)
public class EmployeeSubResource {
private Configuration config;
private int departmentId;
public EmployeeSubResource(int departmentId, Configuration configuration) {
this.departmentId = departmentId;
this.config = config;
}
public EmployeeSubResource() {
}
}
请注意,要将对象序列化为JSON对象,必须有一个默认构造函数。
接下来,让我们定义一个检索某个部门的所有员工的端点:
@GET
public DataResponse<Employee> getAll(@Context UriInfo uriInfo) {
return LinkRest.select(Employee.class, config)
.toManyParent(Department.class, departmentId, Department.EMPLOYEES)
.uri(uriInfo).get();
}
在这个例子中,我们使用了SelectBuilder的toManyParent()方法来仅查询具有给定父级的对象。
可以以类似的方式创建POST、PUT、DELETE方法的端点。
要将员工添加到部门,我们可以使用POST方法调用entities/{departmentId}/employees端点:
curl -i -X POST -H "Content-Type:application/json"
-d "{"name":"John"}" http://localhost:8080/linkrest/department/200/employees
然后我们发送一个GET请求来查看该部门的员工:
curl -i -X GET "http://localhost:8080/linkrest/department/200/employees
这将返回一个带有data数组的JSON对象:
{"data":[{"id":200,"name":"John"}],"total":1}
6. 使用请求参数自定义响应
LinkRest提供了一种通过向请求添加特定参数来自定义响应的简单方法,这些参数可用于过滤、排序、分页或限制结果集的属性集。
6.1 过滤
我们可以使用cayenneExp参数根据属性值过滤结果;顾名思义,这遵循Cayenne表达式的格式。
让我们发送一个仅返回名称为“IT”的部门的请求:
curl -i -X GET http://localhost:8080/linkrest/department?cayenneExp=name='IT'
6.2 排序
对一组结果进行排序需要添加的参数是sort和dir,其中第一个指定排序的属性,第二个指定排序的方向。
让我们看看按名称排序的所有部门:
curl -i -X GET "http://localhost:8080/linkrest/department?sort=name&dir=ASC"
6.3 分页
该库通过添加start和limit参数来支持分页:
curl -i -X GET "http://localhost:8080/linkrest/department?start=0&limit=2
6.4 选择属性
使用include和exclude参数,我们可以控制在结果中返回哪些属性或关系。
例如,我们发送一个仅显示部门名称的请求:
curl -i -X GET "http://localhost:8080/linkrest/department?include=name
为了仅显示部门名称以及员工姓名,我们可以使用include属性两次:
curl -i -X GET "http://localhost:8080/linkrest/department?include=name&include=employees.name
7. 总结
在本文中,我们展示了如何使用LinkRest框架通过REST端点快速公开数据模型。
Post Directory
