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

<<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);
	}
}

 

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

 

代码见附件

 

 

你可能感兴趣的:(sql,Web,bean,SQL Server,REST)