`
ajax
  • 浏览: 253661 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Restlet实战(二十六)事务 (Transaction)

    博客分类:
  • REST
阅读更多

<<Restful Web Service>>这本书再第8章REST和ROA最佳实践的事务章节对如何实现事务是这么写的:

可以把简单的事务暴露为批量操作,或者采用重载的POST,不过还有一种做法,你猜对了,就是事务本身暴露为资源。

 

并结合一个例子给出具体的操作步骤描述:

比如把资金从支票帐户转移到储蓄帐户,比方说“支票帐户”暴露于/accounts/checking/11处,
“储蓄帐户”资源暴露于/accounts/saving/55. 这两个帐户都有50余额。

首先通过向一个事务工厂资源发送POST请求来创建事务:
    POST /transactions/account-transfer HTTP/1.1
    Host: example.com

HTTP响应里给出了我新创建的事务资源的URI:
    201 Created
    Location: /transactions/account-transfer/11a5

用PUT请求构造事务的第一部分:新的、余额增加的帐户。
    PUT /transactions/account-transfer/11a5/accounts/checking/11 HTTP/1.1
    Host: example.com

再用PUT请求构在事务的第二部分:新的余额增加的储蓄帐户
     PUT /transactions/account-transfer/11a5/accounts/saving/55 HTTP/1.1
     Host: example.com

在进行下一步之前,可以随时用DELETE请求删除事务资源,以回滚该事务,不过为了展示例子,
选择提交事务:
    PUT /transactions/account-transfer/11a5
    Host: example.com

  

如果不太明白也没关系,下面我就从服务器和客户端两个点来看看使用Restlet如何实现事务。实现的是上面的转帐的例子。

在我的环境中,baseUri是:http://localhsot:8080/restlet/resources

创建了四个资源:

 

1. TransactionsResource 对应的请求的Uri是/transaction, 这个资源是接到客户端发送的请求,来创建一个事务。

2. TransactionResource 对应的Uri是/transaction/{transactionId}, 这个资源是用作提交事务或者删除事务的。

3. TxnCheckingResource 对应的Uri是/transaction/{transactionId}/accounts/checking/{accountId},这个资源是接收客户端请求,对支票帐户进行相应的映象操作。

4. TxnAccountResource 对应的Uri是/transaction/{transactionId}/accounts/saving/{accountId},这个资源是接收客户端请求,对储蓄帐户进行相应的映象操作。

 

下面模拟客户端请求以及服务器应答的过程,来看看如何实现事务的。

 

首先客户端请求一个事务资源:

Client client = new Client(Protocol.HTTP);

Reference itemsUri = new Reference("http://localhost:8080/restlet/resources/transaction");
Response response = client.post(itemsUri, null);

 请求的资源为TransactionsResource, 我们知道,如果是POST请求,则在Restlet中Resource的acceptRepresentation将被访问到

	@Override
	public void acceptRepresentation(Representation entity)
			throws ResourceException {
		String transactionId = this.generateTransactionId();
		List<Transaction> transactions = Collections
				.synchronizedList(new ArrayList<Transaction>());
		getServletContext().setAttribute(transactionId, transactions);

		getResponse().setStatus(Status.SUCCESS_CREATED);
		getResponse().setLocationRef(getTransactionURI(transactionId));
	}

 上面这段代码,首先产生一个事务Id,然后,构在一个list列表,放到Servlet Context里,至于作用是什么,往下看就慢慢明白。构造完成,设置响应状态为成功创建(201),然后在location header里放置一个事务资源的URI,基于我的测试环境,此URI大概是http://localhost:8080/restlet/resources/transaction/xxxxxxxxx。xxxxxxxx其实就是产生的事务Id,这个Id应该具有唯一性。

 

ok,看看客户端怎么拿到这个事务资源的URI的,接上面客户端的代码:

 

assertEquals(Status.SUCCESS_CREATED.getCode(), response.getStatus().getCode());

Reference transactionUri = response.getLocationRef();

 

好了,如果没有出现问题的话,进行下一步,客户端请求从支票帐户减少50RMB,客户端代码如下:

Reference checkingAccountUri = new Reference(transactionUri + "/accounts/checking/11");
		
StringBuffer entity = new StringBuffer();
entity
	.append("<account id=\"11\">")
	.append("<type>checking</type>")
	.append("<amount>-50</amount>")
	.append("<currency>RMB</currency>")
	.append(
			"<description>debit from checking account which id is 11</description>")
	.append("</account>");
Representation representation = new StringRepresentation(entity
		.toString(), MediaType.TEXT_PLAIN);
response = client.put(checkingAccountUri, representation);

 

这时候,按照上面我们提到的,这次被请求到的资源是TxnCheckingResource,使用的PUT请求,看看服务器是如何处理的:

 

@SuppressWarnings("unchecked")
@Override
public void storeRepresentation(Representation entity) throws ResourceException {
	String transactionId = (String)getRequest().getAttributes().get("transactionId");
	
	Transaction tran = getTransactionFromRepresentation(entity);
	
	List<Transaction> trans = (List<Transaction>)getServletContext().getAttribute(transactionId);
	
	if(trans == null){
		getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
	}else{
		trans.add(tran);
		getServletContext().setAttribute(transactionId, trans);
		getResponse().setStatus(Status.SUCCESS_ACCEPTED);
	}
	
}

 

/*
 * Note: we can receive entity of representation like
 * <account id="11">
 * 	<type>checking</type>
 * 	<amount>-50</amount>
 * 	<currency>RMB</currency>
 * 	<description>debit from checking account which id is 11</description>
 * </account>
 */
private Transaction getTransactionFromRepresentation(Representation entity){
	//Note: we should handle xml body sent from client,
	//then put the relevant info into transaction in product environment.
	//but here we hard code just for testing.
	String accountId = (String)getRequest().getAttributes().get("accountId");
	Transaction tran = new Transaction();
	//tran.setAccountId(accountId);
	tran.setAccountId("11");
	tran.setAccountType("checking");
	tran.setAmount("-50");
	
	return tran;
}

 

是的,服务器接收客户端请求后,分析传过来的表示(Xml),并从中解析出需要的信息,如帐户类型,交易的金额、交易的货币、交易描述等,统一封装到一个Transaction Java bean里。然后将这个Bean放到之前我们放到Servlet Context的一个List里,明白了吧,list的用处就在这里,实际上它也正是《REST Web Service》里提到的动作队列。在这里如果成功的话,返回相应代码为接受(代码为202)。

 

当然客户端接收到的代码应该是202,使用Junit测试返回响应值:

assertEquals(Status.SUCCESS_ACCEPTED.getCode(), response.getStatus().getCode());

 

接着客户端再次发送一个请求,请求往储蓄帐户里加入50RMB:

Reference savingAccountUri = new Reference(transactionUri + "/accounts/saving/55");
entity = new StringBuffer();
entity
	.append("<account id=\"55\">")
	.append("<type>saving</type>")
	.append("<amount>50</amount>")
	.append("<currency>RMB</currency>")
	.append(
			"<description>credit into saving account which id is 55</description>")
	.append("</account>");
representation = new StringRepresentation(entity.toString(),
		MediaType.TEXT_PLAIN);
response = client.put(savingAccountUri, representation);

 

服务器端被访问到的资源是TxnAccountResource,过程跟访问checking account非常类似,不多做解释,不同的代码如下:

/*
 * Note: we can receive entity of representation like
 * <account id="55">
 * 	<type>saving</type>
 * 	<amount>50</amount>
 * 	<currency>RMB</currency>
 * 	<description>Credit into saving account which id is 55</description>
 * </account>
 */
private Transaction getTransactionFromRepresentation(Representation entity){
	//Note: we should handle xml body sent from client,
	//then put the relevant info into transaction in product environment.
	//but here we hard code just for testing.
	String accountId = (String)getRequest().getAttributes().get("accountId");
	Transaction tran = new Transaction();
	//tran.setAccountId(accountId);
	tran.setAccountId("55");
	tran.setAccountType("saving");
	tran.setAmount("50");
	
	return tran;
}

 

 

所有关于帐户的请求已经完成,下面提交事务,首先要客户端发送提交事务的请求:

response = client.put(transactionUri, null);

 请求的URI是/transaction/{transactionId}, 对应的资源如上所述TransactionResource。所以服务器端Resource中代码:

 

@Override
public void storeRepresentation(Representation entity) throws ResourceException {
	HttpServletRequest hsr = ServletCall.getRequest(getRequest());   

	Object obj = hsr.getSession().getServletContext().getAttribute(transactionId);
	if(obj != null){
		List<Transaction> trans = (List<Transaction>)obj;
		//TODO validation should be done here
		// needs to check the resource status and the balance between
		// debit and credit and whether this transaction has been authrized.
		//handleTransaction(trans);
	
		getResponse().setStatus(Status.SUCCESS_OK);
	}else{
		getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
	}
		
}

 

private void handleTransaction(List<Transaction> trans) throws ResourceException{
	DBAccess dba = new DBAccess();
	try {
		dba.setAutoCommit(false);
		dba.stmtBatchUpdate(buildSqls(trans));
		dba.commit();
	} catch (SQLException e) {
		log.error("Excepiotn happened when excuting accounting transfer" + e.getMessage());
		throw new ResourceException(Status.SERVER_ERROR_INTERNAL);
	}
}

 

 

private String[] buildSqls(List<Transaction> trans){
	List<String> sqls = new ArrayList<String>();
	
	for(Iterator ite = trans.iterator(); ite.hasNext();) {   
		Transaction tran = (Transaction)ite.next();
		String sql;
		
		if(tran.getAccountType().equalsIgnoreCase("checking")){
			sql = "UPDATE checking_account SET amount = amount-"
					+ tran.getAmount() + "WHERE id = "
					+ tran.getAccountId();
			sqls.add(sql);
		}else{
			// should be saving account
			sql = "UPDATE saving_account SET amount = amount-"
				+ tran.getAmount() + "WHERE id = "
				+ tran.getAccountId();
			sqls.add(sql);
			
		}
	}
	return (String[])sqls.toArray(new String[sqls.size()]);
}

 

恩,是不是已经明白了,之前的几步都是在根据请求来构造一个动作队列,当客户端请求提交事务时候,这个时候会真正的去启动一个数据库事务,然后执行队列里的动作,最后试图提交数据库事务,如果数据库事务出错,则Web事务也不会成功。 

 

至此,一个正常的事务流程就完美的结束了,等等,问题来了,如果我请求了事务资源,然后我也请求了对帐户进行操作,但是最后我又不需要了,换句话说,我想删除事务,恩,同样请求的URI是:/transaction/{transactinId}, 使用DELETE方法:

 

@Override
public void removeRepresentations() throws ResourceException {
	Object obj = getServletContext().getAttribute(getTransactionURI(transactionId));
	if(obj != null){
		getServletContext().removeAttribute(getTransactionURI(transactionId));
	}
		getResponse().setStatus(Status.SUCCESS_OK);
	}
}

 

当然了,基于上述的一个最后提交的事务,在提交成功后,系统应该自动的删除事务资源。

 

代码见附件

 

 

分享到:
评论
1 楼 shihezichen 2014-05-18  
对于最近很多人都在讨论的, 使用REST时就不应该掺杂事务的看法和观点,你怎么看?
例如下面这个:
http://blog.sina.com.cn/s/blog_4ba0876e01014y44.html

相关推荐

    restlet

    RESTlet是一款开源框架,专为构建基于REST(Representational State Transfer)原则的Web服务和应用程序设计。REST是一种轻量级的架构风格,广泛应用于互联网应用的开发,它强调通过简单的HTTP方法(如GET、POST、...

    Restlet开发实例

    接下来,我们进入"RESTLET开发实例(二)使用Component、Application的REST服务.doc"。在Restlet框架中,Component是整个应用的基础,它负责管理和协调多个Application。Application则是实际处理HTTP请求的实体,它...

    Restlet与Spring 集成

    Restlet与Spring集成是将Restlet框架与Spring框架结合,以增强RESTful服务的开发能力。Restlet是一个轻量级的Java框架,专门用于构建REST(Representational State Transfer)架构风格的应用程序。它遵循JAX-RS...

    org.restlet.jar

    org.restlet.jar

    restlet2.1学习笔记项目代码

    Restlet是一个开源框架,专为构建RESTful(Representational State Transfer)Web服务而设计。REST是一种轻量级的架构风格,常用于构建可扩展、高性能的互联网应用程序。本项目是针对Restlet 2.1版本的学习笔记,...

    Restlet开发的Basic认证

    Restlet是一个轻量级的Java Web服务开发框架,它提供了构建RESTful(Representational State Transfer)应用程序的工具和API。REST是一种架构风格,强调简洁、无状态和可缓存的网络交互,常用于构建高性能、高可用性...

    Restlet所需要的所有jar包

    Restlet是一款开源的Java框架,专门用于构建RESTful(Representational State Transfer)Web服务。REST是一种轻量级的架构风格,常用于构建高效、可扩展的网络应用程序。本压缩包包含Restlet框架运行所需的全部jar...

    RESTLET开发

    ### RESTLET开发实例详解 #### 一、RESTLET框架简介 **RESTLET**是一个轻量级且全面的框架,旨在实现REST(Representational State Transfer)原则与Java类之间的映射。该框架支持广泛的REST式系统构建,不仅仅...

    Restlet Client 插件安装包

    **Restlet Client插件安装包详解** 在现代的Web应用开发中,API(Application Programming Interface)扮演着至关重要的角色,尤其是RESTful API,它已成为构建分布式系统和微服务架构的标准。为了有效地测试和调试...

    org.restlet-2.3.0.jar 最新版本

    《深入理解org.restlet-2.3.0.jar:Java RESTful开发的得力助手》 在Java开发领域,REST(Representational State Transfer)架构风格已经成为构建Web服务的主流选择,而org.restlet-2.3.0.jar则是实现RESTful API...

    restlet实现最简单的restful webservice

    Restlet是一款开源框架,专为构建RESTful Web服务和客户端应用程序设计。REST(Representational State Transfer)是一种轻量级的架构风格,广泛用于构建互联网应用,因为它提供了高效、分布式的系统设计原则。本文...

    restlet项目

    Restlet项目是一个开源框架,专门用于构建RESTful(Representational State Transfer)Web服务。REST是一种软件架构风格,它强调简洁、可扩展性和无状态性,是Web服务设计的主流方式。Restlet框架提供了全面的工具集...

    Restlet in action 英文 完整版

    #### 六、安全Restlet应用 第五章“Securing a Restlet application”则深入探讨了如何保护Restlet应用免受安全威胁。内容包括但不限于身份验证机制、授权策略以及如何实施加密技术来保障数据的安全传输。对于任何...

    RESTLET框架学习书籍

    #### 二、RESTLET框架的基础架构 - **RESTLET的基本概念**: - **Resources**:表示RESTLET中的资源,是RESTLET应用的核心组件,用于处理特定URL的HTTP请求。 - **Filters**:过滤器用于拦截请求和响应,实现如...

    restlet1.1文档

    #### 二、Restlet的特点与应用场景 1. **统一的开发环境**:Restlet提供了一个统一的环境来处理不同的HTTP请求方法,使得开发者能够更专注于业务逻辑而不是底层协议。 2. **改进的部署灵活性**:Restlet允许开发者...

    Restlet in action 中文

    【Restlet in Action 中文】这本书是一本针对Java开发者介绍Restlet框架的实战指南,旨在帮助读者更好地理解和使用RESTful Web API。Restlet是一个开源的Web框架,它以面向对象的方式提供了一系列类和示例,使得...

Global site tag (gtag.js) - Google Analytics