使用 Apache Wink 和 OpenJPA 构建 REST 资源

Apache Wink 是一个用于构建 REST 的框架。本文将向您介绍如何使用 Wink 和 OpenJPA(Java 持久化架构)来实现、存储、检索和更新资源。除此之外,您还将学习如何使用 OpenJPA 持久保存资源。本文示例将向您展示如何使用 Wink 和 OpenJPA 从头至尾完成 CRUD(创建、检索、更新和删除)操作。

Sathish Maiya, 资深软件工程师, IBM

http://www.ibm.com/developerworks/i/p-smaiya.jpgSathish Maiya 是印度 IBM Software Labs 的一名资深软件工程师,在 WebSphere Partner Gateway 团队工作。他拥有印度迈索尔大学计算机科学工程学位。Sathish 也拥有印度 Mangalore 大学的生物医学工程硕士学位。他在软件设计、开发和测试方面有丰富经验。



Anish Chaube, 软件工程师, IBM

//www.ibm.com/developerworks/i/p-achaube.jpgAnish Chaube 是印度 IBM Software Labs 的 WebSphere Partner Gateway 团队的一名助理软件工程师,他拥有 Bangalore 国际信息技术学院信息技术硕士学位。Anish 在软件开发和测试方面有丰富经验。



Pranab K Das, 软件工程师, IBM

//www.ibm.com/developerworks/i/p-pdas.jpgPranab Das 是印度 IBM Software Labs 的 WebSphere Partner Gateway 团队的一名软件工程师。他拥有贝拿勒斯大学计算机科学学士学位。Pranab 在软件开发和测试方面有丰富的经验。



Dilip Sathyanarayan, 软件工程师, IBM

/developerworks/i/p-dsathyanarayan.jpgDilip Sathyanarayan 是印度 IBM Software Labs 的一名高级软件工程师。他在软件开发、测试/自动化测试、以及技术文档方面有丰富的经验。目前,他是正在纽约圣母学院攻读他的信息系统硕士学位。



2012 年 8 月 23 日

简介

具象状态传输 (Representational State Transfer,REST) 是分布式超媒体系统(比如 World Wide Web)的一种软件构架风格。资源或者特定信息来源是 REST 的一个重要组成部分。每一资源通过全局标识符(例如,HTPP 中的 URI)引用。想要对资源进行操作,网路组件(称为用户代理和伺服器)使用标准化接口 (HTTP) 通信以及交换资源表现方式。

常见缩略词

  • CRUD:创建、检索、更新、删除
  • HTTP:超文本传输协议
  • REST:具象状态传输
  • URI:统一资源标志符
  • XML:可扩展标记语言

Apache Wink 是一个框架,您可用来构建 RESTful Web 服务。根据 REST 体系结构风格,Wink 通过提供构建服务的方式促进 REST Web 服务的开发和消耗。Wink 提供必要的架构来定义和实现资源、表现形式和构成服务的统一方法。Apache OpenJPA 框架是一个开源软件,定义了一个资源如何实现持久化。

在本文中,使用 Wink 和 OpenJPA 通过一个 REST 服务在资源上实现 HTTP 操作,在一个样例资源中学习建模、URI 设计以及使用 Wink 注释实现创建、检索、更新和删除 (CRUD) 操作。

您可以在此 下载 本文所用源代码。

资源建模

每个资源必须被建模成一个具体结构,比如 XML 和 JSON。该资源的所有属性和值将使用相应 XML 属性名和值映射。以本文为例,让我们考虑将 “Employee” 作为一个资源,每个 Employee 拥有惟一的属性,比如员工 ID,员工名字、员工地址、员工邮箱、以及员工电话号码。清单 1 显示了 Employee 资源的 XML 结构。

清单 1. Employee 资源的 XML 结构
<employee>		
     <employeeId>101</employeeId>
     <employeeName>employee Name</employeeName>
     <address>XYZ C Block, Bangalore-560025</address>
     <email>employee email</email>
     <telephone>5551234567</telephone>
</employee>

Apache Wink 框架支持 JAXB 注释来将 XML 或 JSON 编组和解组到一个 JAVA 对象中,反之亦然。OpenJPA 为持久化 JAVA 对象进入关系型数据存储提供一个框架。


实体建模

OpenJPA 中的实体是持久化对象,表示数据存储记录。如本文示例所示,_Employee 是实体模型,表示 Employee 对象。实体类 _Employee 的每个持久化实例表示一个惟一的数据存储记录(惟一员工)。您可以使用注释定义实体模型。在 Employee 案例中,@Table (name="CF_EMPLOYEE") 用于定义 Employee 实体。

所有实体类必须声明一个或多个字段,共同构成一个实例的持久化标识。在我们的示例 employee 中,employeeId 被用作惟一标识符。OpenJPA 使用实体中的 version 字段检测同一数据存储记录的并行修改。使用 @Version 注释。清单 2 中的代码摘要显示了如何为 Employee 资源创建一个实体模型。

清单 2. Employee 资源的 OpenJPA 模型
package openJpa.model;

/**
 * Persistent Class for Employee
 * OpenJPA enables writing persistent classes in a very transparent way
 */

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Version;

@Entity
/*Each entity represents a relational datastore record.
In this code all employee records will be stored in a table called CF_EMPLOYEE */
@Table(name="CF_EMPLOYEE")
/*
Databases allow Native sequences to be created , which are database
structures that generates increasing numeric values.
SequenceGenerator annotation represents a named database Sequence
*/
@SequenceGenerator(name="employeeSeqGen", sequenceName="native(Sequence=CF_EMPLOYEE_SEQ)")
public class _Employee implements Serializable {
	
	private static final long serialVersionUID = 1L;
	public _Employee() {
		super();
	}
	
/*All entity classes must declare one or more fields which together 
forms the persistent identity of an instance.Every employee will have an
unique employeeID which is the persistent identity */
	@Id
	@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="employeeSeqGen")
	private int employeeId;
/* OPEN JPA uses version field in entities to detect concurrent 
modifications to the same datastore record.Version field is optional
for any entity, but without one concurrent threads or processes might succeed
in making conflicting changes to the same record at the same time. */

	@Version
	private int version;
	
	private String employeeName;
	private String address;
	private String email;
	private int telephone;
	
	public void setEmployeeId(int employeeId) {
		this.employeeId = employeeId;
	}
	public int getEmployeeId() {
		return employeeId;
	}
	public void setVersion(int version) {
		this.version = version;
	}
	public int getVersion() {
		return version;
	}
	public void setEmployeeName(String employeeName) {
		this.employeeName = employeeName;
	}
	public String getEmployeeName() {
		return employeeName;
	}
	public void setAddress(String address) {
		this.address = address;
	}
	public String getAddress() {
		return address;
	}
	public void setEmail(String email) {
		this.email = email;
	}
	public String getEmail() {
		return email;
	}
	public void setTelephone(int telephone) {
		this.telephone = telephone;
	}
	public int getTelephone() {
		return telephone;
	}
}

获取 OpenJPA 数据存储链接

建立实体模型之后,需要实现连接逻辑来建立与您数据存储(可以是任意类型的数据库)的连接。您可以编写一个包含逻辑的独立层连接 OpenJPA 与数据库。一个比较简单的方法是将所有关系型数据库配置放在一个属性文件中,然后定义一个类,比如 OpenJpaAdminService,它可以提取数据库连通性。连接数据库的逻辑使用 getEntityManager() 方法。每当它从 OpenJpaAdminService 中调用时,您可以查找属性文件,以便 OpenJPA 可以知道连接到哪个数据库。

下载 部分提供的示例中,OpenJpaMediator.java 是一个可以处理所有 OpenJPA 操作的类。在这个类构造器中,调用 OpenJpaAdminService.getEntityManager() 函数检查属性文件 dbConnection.properties。您可以通过两种方法连接一个数据库:

  • 典型模式中,您可以使用 JDBC 驱动器细节进行连接。
  • 使用 JNDI 查找获取一个 JDBC 连接(参见 下载 文件中的 OpenJpaAdminService.java)。

REST 资源建模

Apache Wink 是一个用于 REST 服务(通过惟一 URI 识别的资源)的简单实现和消耗的框架。每个资源有一个或多个表现方式,在 Web 服务调用期间可在客户端和服务之间转换。Wink 提供:

  • 在 RESTful 体系结构风格中定义和实现资源的必要基础架构,进而促进统一接口、多种表示形式和服务自省。
  • REST Web 服务的 API 功能支持。Wink 是一个基于 Java 的 API,它使用注释简化 Web 服务客户端和终端的开发和部署。

尽管使用 servlet 可以实现 RESTful 服务,但是总是需要执行太多 HTTP 代码来实现业务逻辑。Wink 隐藏了所有 HTTP 代码并将 servlets 绑定到 Java 类的单个方法中。


表示一个资源类

资源可以表示一个可支持数据检索和操作的可用组件。资源类可以定义帮助实现事务逻辑的资源方法。清单 3 显示如何为 Employee 资源创建一个资源类。

终端用户总是以 XML 或 JSON 方式来表示一个资源。当终端用户需要创建或更新资源的一个新实例时,它们传递该资源相应的 XML 或 JSON。资源类中的 Wink 注释通常将 XML 或 JSON 编组到一个 JAVA 对象中。定义一个实体对象 (_Employee) 实例的好处是可以在终端编组 XML 或 JSON,您拥有一个已经保存到数据存储中的实体对象。因此,您可以使用 Wink 框架的这一功能来持久化一个 OpenJPA 实体对象。类似地,当用户想要检索该资源的某一实例时,OpenJPA 将从数据存储中检索该资源,然后提供实体对象。Wink 注释帮助将检索到的实体对象解组到 XML 或 JSON 中并将其发送给终端用户。

清单 3. 资源表现方式
package rest.resource;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import openJpa.model._Employee;

/**
 * @XmlAccessorType Controls whether fields or Javabean properties are 
 * serialized by default
 * XmlAccessType.PROPERTY: Every getter/setter pair in a JAXB-bound class will
 * be automatically bound to XML, unless annotated by XmlTransient. Fields are 
 * bound to XML only when they are explicitly annotated by some of the JAXB 
 * annotations.
 *
 */
 
@XmlAccessorType(XmlAccessType.PROPERTY)
/**
 * @XmlRootElement: Maps a class to an XML element
 */
 
@XmlRootElement(name="employee")
public class Employee {
/**
 * Instantiate Enity Object of Employee to achieve integration of APACHE WINK 
 * and OPEN JPA	
 */
private _Employee modelEmployee;
	
	public Employee() {
		super();
		this.modelEmployee = new _Employee();
	}
	
	public Employee(_Employee modelEmployee) {
		this.modelEmployee = modelEmployee;
	}
	
	@XmlTransient
	public _Employee getEmployee() {
		return this.modelEmployee;
	}
	public void setEmployee(_Employee modelEmployee)
	{
		this.modelEmployee = modelEmployee;
	}
	/**
	@XmlElement: Maps a JavaBean property to a XML element derived from property name
	*/
	@XmlElement(name = "employeeName")
	public String getEmployeeName() {
		return modelEmployee.getEmployeeName();
	}
	
	public void setEmployeeName(String employeeName) {
		modelEmployee.setEmployeeName(employeeName);
		
	}
	
	@XmlElement(name = "address")
	public String getAddress() {
		return modelEmployee.getAddress();
	}
	
	public void setAddress(String address) {
		modelEmployee.setAddress(address);
	}
	
	@XmlElement(name = "email")
	public String getEmail() {
		return modelEmployee.getEmail();
	}
	
	public void setEmail(String email) {
		modelEmployee.setEmail(email);
	}
	
	@XmlElement(name = "telephone")
	public int getTelephone() {
		return modelEmployee.getTelephone();
	}
	
	public void setTelephone(int telephone) {
		modelEmployee.setTelephone(telephone);
	}
	
	@XmlElement(name = "employeeId")
	public int getEmployeeId() {
		return modelEmployee.getEmployeeId();
	}
	
	public void setEmployeeId(int employeeId) {
		modelEmployee.setEmployeeId(employeeId);
	}
	
	@XmlElement(name = "version")
	public int getVersion() {
		return modelEmployee.getVersion();
	}
	
	public void setVersion(int version) {
		modelEmployee.setVersion(version);
	}
}

CRUD 操作

本小节介绍可以使用 Wink 框架和 OpenJPA 在 Employee 资源上执行的基本 CRUD 操作。对于每个操作,有一个惟一的 URI 和一个 HTTP 方法。对于创建和更新操作,资源数据(Employee 详细资料)必须作为 Content-Body 表示。Employee 资源的 Content-Body 可以是 JSON 或 XML。Apache Wink 支持 XML、JSON、HTML 和 Atom。表 1 显示 CRUD 操作的设计。

表 1. CRUD 操作
操作描述资源/URI资源数据
CREATE包括用户创建一个新资源的设备。在该示例中,操作包括创建一个新 Employee 或添加一个新 Employee 到现有数据库。

HTTP 方法:POST
/employeeContent-Body 以一种特别的表现形式包括 Employee 详细资料。每个 RESTful 接口可以为其客户端决定它所支持的表现方式类型。例如 清单 1中的 XML 结构可以使用 Content-Body 来创建一个新 Employee。
RETRIEVE允许用户通过 Employee Id 检索现有员工的详细资料。检索数据可以以不同的表示方式(客户端 REST 支持的)来表示。例如,/employee/121: 检索 ID 为 121 的员工的详细资料。

HTTP 方法:GET
/employee/{id}Content-Body 不能为 GET 方法所调用。GET 应该是一个安全的只读幂等(idempotent)调用,它不应该以任何方式更改资源的状态。
UPDATE包括一个可供用户更新现有资源的设备。在本例中,该操作包括更新数据中现有的 Employee。

HTTP 方法:PUT
/employeeContent-Body 以一种特别的表现形式包括 Employee 详细资料。每个 RESTful 接口可以为其客户端决定它所支持的表现方式类型。例如 清单 1 中的 XML 结构可以使用 Content-Body 来更新一个 Employee。
DELETE允许用户通过 Employee Id 删除一个现有员工的详细信息。删除后,用户将得到一个关于该员工信息是否成功删除的 HTTP 响应。例如,/employee/121: 删除 ID 为 121 的员工的详细资料。

HTTP 方法:DELETE
/employee/{id}Content-Body 不能为 DELETE 方法所调用。

action 类样例

action 类定义一个资源可以实现的所有 HTTP CRUD 操作的实现。Apache Wink 收到 HTTP 请求,然后将一个包装的 HTTP 请求分配到适当的方法(写入 action 类中的)。每个 HTTP 请求应该映射到 action 类的一个方法中,具体根据 HTTP 请求参数、资源方法定义以及 MIME 类型而定。

清单 4 显示如何为 Employee 资源创建一个 action 类。在本示例中,可以创建一个 EmployeeAction 类来实现 Apache Wink RESTful 服务。它由 4 个方法组成,每个代表一个 CRUD 操作。

清单 4. Employee action 类实现
package rest.action;

import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import openJpa.mediator.EmployeeOpenJpaMediator;
import openJpa.model._Employee;
import rest.resource.Employee;

@Path(value = "/employee")
public class EmployeeAction {
	
	/**
	 * @param employee
	 * CREATE: @return This method is used to create new Employee
	 */
	@POST
	@Consumes(value = { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
	@Produces(value = { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
	public Response persistEmployee(Employee employee) {
		EmployeeOpenJpaMediator dao = new EmployeeOpenJpaMediator();
		_Employee _employee = employee.getEmployee();
		try {
			dao.beginTransaction();
			dao.persist(_employee);
			dao.commitTransaction();

		}  catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			dao.rollbackTransaction();
			return Response.status(400).entity(
					"Employee create failed!").build();
		} finally {
			dao.close();
		}
		Employee employeeCreated = new Employee(_employee);
		return Response.ok(employeeCreated).build();
	}
	
	/**
	 * @param id
	 * RETRIEVE: @return This method is used to retrieve a particular Employee 
	   through employeeId
	 */
	@GET
	@Path("/{id}")
	@Produces(value = { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
	public Response findEmployee(@PathParam(value = "id") int id) {

		EmployeeOpenJpaMediator employeeDao = new EmployeeOpenJpaMediator();
		_Employee u = new _Employee();
		try {
		if ((u = (_Employee) employeeDao.getById(_Employee.class, id)) != null) {
			return Response.ok(new Employee(u)).build();
		} else {
			return Response.status(404).entity(
			"Employee id does not exist").build();
		}
		}catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			return Response.status(404).entity(
					"Employee id does not exist").build();
		}
	}
	
	/**
	 * @param employee
	 * UPDATE: @return this method is used to update a particular Employee 
	   already existing
	 */
	@PUT
	@Produces(value = { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
	@Consumes(value = { MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
	public Response updateEmployee(Employee employee) {
		EmployeeOpenJpaMediator ud = new EmployeeOpenJpaMediator();
		_Employee _employee = employee.getEmployee();
		try {
			ud.beginTransaction();
			ud.update(_employee);
			ud.commitTransaction();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			ud.rollbackTransaction();
			return Response.status(409).entity(
					"Employee update failed!").build();

		} finally {
			ud.close();
		}

		Employee employeeUpdated = new Employee(_employee);
		return Response.ok(employeeUpdated).build();
	}
	
	/**
	 * @param id
	 * DELETE: This method is used to delete a particular Employee through employeeId
	 */
	@DELETE
	@Path("/{id}")
	public Response deleteEmployee(@PathParam(value = "id") int id) {
		EmployeeOpenJpaMediator pd = new EmployeeOpenJpaMediator();
		_Employee _employee = (_Employee) pd.getById(_Employee.class, id);
		if(_employee!=null){
		try {
			pd.beginTransaction();
			pd.delete(_employee);
			pd.commitTransaction();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			pd.rollbackTransaction();
			return Response.status(400).entity(
					"Employee delete had an error!").build();
		} finally {
			pd.close();
		}
	return Response.ok().build();
		}
		else{
			return Response.status(400).entity(
			"Employee ID does not exist!").build();
		}
	}
}

Action 类调用程序

每当一个 REST servlet 被初始化时,RestApp 类(实际上扩展了 Application 类)将被调用。RestApp 可以帮助 Apache Wink 获取相应 action 类并执行相应 HTTP 操作,正如在 action 中所编码的。因此 action 类是注册在框架中的。清单 5 显示如何为 EmployeeAction 类创建一个 action 类调用程序。

清单 5. Employee action 类实现,续
 package rest.app;

import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;
import rest.action.EmployeeAction;

public class RestApp extends Application {
	@Override
	public Set<Class<?>>: getClasses() {
		Set<Class<?>> classes = new HashSet<Class<?>>();
		addResources(classes);
		return classes;
	}
	
	public Set<Class<?>> addResources(Set<Class<?>>classes)
	{
		classes.add(EmployeeAction.class);
		return classes;
	}
	
}

结束语

在本文中,您学习了 REST architecture、OpenJPA 实现和 Apache Wink 标准如何帮助简化 RESTful 服务实现。OpenJPA 和新 Apache Wink 框架的集成只通过一个 REST 服务即可帮助您在资源上实现 HTTP 操作。您已经学习了 OpenJPA 实体建模、资源建模、URI 设计以及使用 Wink 注释在一个样例资源上实现 CRUD 操作。


下载

描述名字大小
使用 OpenJPA 对 REST 资源进行建模的代码OpenJPA.zip9KB

参考资料

学习

获得产品和技术

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development
ArticleID=831426
ArticleTitle=使用 Apache Wink 和 OpenJPA 构建 REST 资源
publish-date=08232012