`
klyuan
  • 浏览: 184563 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

一个特殊的异常处理

    博客分类:
  • java
阅读更多
一个特殊的异常处理
文:袁光东

一、业务需求说明
前段时间接到一个关于援助卡的需求。这个需求比较特殊的是援助卡的卡号是由单证系统进行管理的。
可能用银行卡来说明比较清楚一些。
银行卡是由银行统一制作出来的,每一个银行卡号都是事先已经编制了的。银行有一个单证部门来管理这些印刷的银行卡。各个营业点,会向单证部门领取银行卡。此时单证系统的银行卡为领取状态。
当一个储户去银行开户时,银行职员会为储户选择一张银行卡。这张银行卡的卡号就成了储户的帐号。
计算机系统完成开户处理需要有三个步骤:
1.在单证系统中检查卡号是否存在,并且卡号为已领取(要是印刷错误呢,呵呵)。这个过程称为回销检查。
2.然后把该卡号作为帐号和开户人的相关信息都写进帐户信息表去。
3.同时还需要调用单证系统,把该卡号的状态改为使用状态。这个过程称为回销。

援助卡的需求跟银行开户处理一样。


二、代码实现
public OperateResultDTO issueAssistanceCard(AssistanceCardDTO assistanceCardDTO)
			throws BusinessServiceException {
		…..省略
			OperateResultDTO operateResultDTO;
			operateResultDTO = service.writeOffCheck(
					assistanceCardDTO.getCardNo());// 检查卡号是否可回销
			if (!operateResultDTO.isSuccess()) {
              ….省略部分代码
				
			}
			service.issueAssistanceCard(assistanceCardDTO);//新发卡
			operateResultDTO = service.writeOff(assistanceCardDTO
					.getCardNo());// 将新卡回销
		 
		} catch (BusinessException ex) {
			throw new BusinessServiceException(ex);
		}
	}


注意:回销检查和回销操作并没有调用外部接口,而是调用财务部门提供的一个PACAKGE方法。当然,这个procedure我们是没有权限看的。但是基本逻辑是知道的。

三、发生的问题
看上面的代码很简单。但是其中有一个非常严得的问题。就是事务的原子性。
在第二步,把援助卡信息写进了援助信息表。然后调用单证系统的回销接口,把卡号进行回销。如果此时回销失败。Prodedure返回的结果是N. 在java方法中返回的结果operateResultDTO.isSuccess为false;
如果就这样操作的话,虽然回销失败了,但是援助卡信息还是被成功的写进了数据库。这就违背了事务的原子性原则。
在一个事务里的操作,要么全部成功,要么全部失败。

四、解决方法一
要想达到事务的原子性,当回销失败时,就必须回滚它之前所有的操作。别要跟我说调用delete方法,把插入的数据删除掉哦。
最简单的办法就是回销操作失败时,抛出异常。

			operateResultDTO = service.writeOff(regionCode, assistanceCardDTO
					.getCardNo());// 将新卡回销
		 
		if(! operateResultDTO.isSuccess()){
			throw new BusinessServiceException(“回销卡号失败,卡号:”+cardNo);
		}

这样处理了之后,就解决了事务的原子性特性。
但是,当抛出了这个异常之后,程序也就当掉了。不可恢复了。但这张卡失败大不了给用户重新选一个卡号就完了嘛。但是还得重新录入一大堆客户资料信息吧。

五、解决方法二
为了让程序更加健壮,当回销操作失败后,应该再重新回到录入页面,让操作人员为客户重新选择一张卡。并且以前录入的信息也自动带出。并抛出一个异常是不够的。
可能你会说:那在controller 捕获BusinessServiceException异常不就完了嘛。如果有这个异常就返回到之前的页面。
在controller代码写上
	try{
		调用action
	}catch(BusinessServiceException ex){
		ModelAndView mav = new ModelAndView(“原来的录入页面”);
        mav.addObject(“录入信息DTO”);//我简单的写一下伪代码
		return mav;
	}


这算是说到了关键点上了。但是这解决不了问题。因为你根本捕获不了BusinessServiceException.
框架对业务异常进行了处理。把业务异常转为了web异常。
所以应该写成这样。
catch (PafaWebException ex) {
			if (ex.getCause() instanceof  BusinessServiceException)
			{
				errorMap.put("reissueFail", "回销卡失败!");
				mav.addObject("error", errorMap);
				mav.addObject("assistanceCardDTO", assistanceCardDTO);
				return mav;
			} else {
				throw ex;
			}

似乎这样就已经解决了吧。事务的原子性得到了保证,程序的健壮性也得到了增强。
但是,如果某一天有业务方法的其它地方也抛出了一个BusinessServiceException时。而不是由回销失败起发的异常。你也让它重试,也提示他回销失败。那就摆了个大乌龙了。

解决方法三:
为了避免大乌龙的出现,必有抛出一个异常确认是由回销失败引起的。而且还需要把这个异常继承于BusinessServiceException.否则你就需要破坏原来接口的签名。
public static class WriteOffException extends BusinessServiceException {
		public WriteOffException() {
			super();
		}

		public WriteOffException(String msg) {
			super(msg);
		}

		public WriteOffException(String msg, Throwable cause) {
			super(msg, cause);
		}
	}
我这里是把它定义为一个内部的静态类。

然后业务方法不再是抛出BusinessServiceException.了。
if (operateResultDTO.isSuccess()) {
				//回销成功
…..
			} else {
				throw new WriteOffException("回销卡号失败:"
						+ assistanceCardDTO.getCardNo()
						+ operateResultDTO.getMessage());
			}


在controller里也只是稍加变化。
catch (PafaWebException ex) {
			if (exceptionContainCause(ex, WriteOffException.class)) // 如果抛出的异常属于回销失败而抛出的
			{
				errorMap.put("reissueFail", "回销卡失败!");
				mav.addObject("error", errorMap);
				mav.addObject("assistanceCardDTO", assistanceCardDTO);
			} else {
				throw ex;
			}
	private boolean exceptionContainCause(Exception ex, Class exClass) {
		if (null == exClass) { 
			return false;
		}
		if (null == ex) {
			return false;
		}
		Throwable exSource = ex;
		while (exSource != null) {			
			if (exClass.isInstance(exSource)) {
				return true;
			}
			exSource = exSource.getCause();
		}
		return false;
	}

需要注意的是:只有异常是由WriteOffException这种异常引发的时才处理。否则继续抛出。
另外要注意exceptionContainCause方法。该方法是看一个异常是属于某个类型的异常。从当前开始比较,如果不匹配就拿该异常类的父类去比较。
这样问题就获得了圆满的解决。即保证了事务的原子性,又加强了程序的健壮性。

总结:
在实现业务方法,进行多种操作或调用外部接口时,一定要考虑事务原子性属性。有些时候是要你手动来保证的。
并于异常的处理,有时不是简单的抛出异常草草了事,能够提供给用户重试机会的,就需要给用户重试的机会。提高程序的稳定性和健壮性。







分享到:
评论
5 楼 fjlyxx 2008-12-14  
如果在没有业务限制的情况下,我觉得不应该用procedure。procedure更优这句是有前提的,在压力小的系统中是这样的。如果你有集群那么你是否把这个压力都交给数据库了。这是一个陷阱。

基于规范访问调用的平台会比你用procedure更优,当然你的应用现状这样也是没有办法的。
4 楼 klyuan 2008-12-14  
fjlyxx 写道
klyuan 写道
fjlyxx 写道

我觉得  你说的事务原子性 其实应该在设计的时候就进行分解,你的问题不是出在异常怎么处理上而是业务分解的不合理。对业务颗粒度的分解把握不恰当,你的异常处理其实是在补救设计上的问题而已。


如果你遇到大型系统,就会深入感受了。一些方法是由其它项目组提供的。并且你不能自己去写一套。因为这个方法的逻辑是由其它项目组来维护。同时这个方法并是以procedure方式来实现的。你只能调用这个procedure.

这不是补救的问题。


呵呵 ,怎么不考虑发布成一个服务了,其他业务提供的方法 最好能发布成一个服务。靠procedure本来就是一个不明智的做法,安全性和可维护性,还有访问标准问题都得不到解决。在大型项目中这个更容易出问题。如果你这个项目在SOA平台上 也许你要做的只是写一个简单的BPEL脚本。


soa平台并不能够解决掉这个问题。从性能上来说,procedure更优。
另外公司的访问规范是不可以轻易违反的。
3 楼 fjlyxx 2008-12-14  
klyuan 写道
fjlyxx 写道

我觉得  你说的事务原子性 其实应该在设计的时候就进行分解,你的问题不是出在异常怎么处理上而是业务分解的不合理。对业务颗粒度的分解把握不恰当,你的异常处理其实是在补救设计上的问题而已。


如果你遇到大型系统,就会深入感受了。一些方法是由其它项目组提供的。并且你不能自己去写一套。因为这个方法的逻辑是由其它项目组来维护。同时这个方法并是以procedure方式来实现的。你只能调用这个procedure.

这不是补救的问题。


呵呵 ,怎么不考虑发布成一个服务了,其他业务提供的方法 最好能发布成一个服务。靠procedure本来就是一个不明智的做法,安全性和可维护性,还有访问标准问题都得不到解决。在大型项目中这个更容易出问题。如果你这个项目在SOA平台上 也许你要做的只是写一个简单的BPEL脚本。
2 楼 klyuan 2008-12-14  
fjlyxx 写道

我觉得  你说的事务原子性 其实应该在设计的时候就进行分解,你的问题不是出在异常怎么处理上而是业务分解的不合理。对业务颗粒度的分解把握不恰当,你的异常处理其实是在补救设计上的问题而已。


如果你遇到大型系统,就会深入感受了。一些方法是由其它项目组提供的。并且你不能自己去写一套。因为这个方法的逻辑是由其它项目组来维护。同时这个方法并是以procedure方式来实现的。你只能调用这个procedure.

这不是补救的问题。
1 楼 fjlyxx 2008-12-14  
我觉得  你说的事务原子性 其实应该在设计的时候就进行分解,你的问题不是出在异常怎么处理上而是业务分解的不合理。对业务颗粒度的分解把握不恰当,你的异常处理其实是在补救设计上的问题而已。

相关推荐

    C++异常处理技巧try/catch/throw/finally/exception

    结构化异常处理是Windows平台上的一个特殊异常处理机制,主要用于处理特定类型的异常,如访问违规或非法指令等。对于Windows程序员来说,掌握SEH是非常必要的,因为它可以帮助开发者处理那些可能无法通过普通的C++...

    MySQL定义异常和异常处理详解

    MySQL中的异常处理是数据库编程中不可或缺的一部分,它允许开发者预设对可能出现的错误或异常的响应,从而确保程序的稳定性和健壮性。在MySQL中,异常定义和处理主要是通过`DECLARE`语句来实现的。 1. **异常定义**...

    ARM处理器异常处理步骤

    异常处理的第一步是保存当前处理器的状态,称为CPSR(当前程序状态寄存器),以便异常处理完成后能恢复到异常发生前的状态继续执行程序。 异常处理的基本步骤包括以下几个关键点: 1. 将下一条指令的地址存入连接...

    JCVM异常处理机制

    具体而言,一个异常处理表项可能包含以下字段: - **start_pc**:异常处理范围的起始字节码索引。 - **end_pc**:异常处理范围的结束字节码索引。 - **handler_pc**:处理异常的字节码指令的位置。 - **catch_type**...

    浅析JAVA异常处理机制.pdf

    异常处理是Java语言中的一个重要机制,它能够确保程序在遇到不可预料的情况时仍能维持稳定运行。异常处理主要包括三个方面:捕获异常、控制程序流程以及定义异常处理语句块。正确合理地运用这些机制对于提高软件系统...

    使用纯C语言实现异常处理

    这个例子展示了如何在C语言中实现一个简单的异常处理框架。通过这种方式,我们可以在程序中集中处理错误,使代码更易于理解和维护。当然,这只是一个基础模型,实际应用中可能需要扩展这个框架,比如添加堆栈回溯、...

    金融系统常用异常处理

    综上所述,金融系统异常处理是一个涉及多个层面的复杂任务,需要综合运用各种技术手段和管理策略,以确保系统的稳定、安全和高效运行。在实际操作中,还需要不断学习和适应新的技术和挑战,持续提升金融系统的异常...

    工作流系统异常处理实现方法

    考虑到人员在工作流系统中的严格角色和权限定义,单靠部分参与者可能无法完成一个异常处理过程,所以除了异常的自动传播机制外,如何在系统的组织层次上实现异常处理过程的协调合作也是非常重要的。 #### 5. 结合...

    高效java异常处理机制

    一个好的编程实践是通过抛出异常来报告错误,而不是返回特殊的错误代码。异常提供了更清晰的语义,使得代码更易于理解和维护。 3. **避免过多的try-catch块** 过多的try-catch块会使代码变得复杂且难以阅读。尽量...

    C#异常处理

    异常处理是现代编程语言中一个重要的组成部分,它能够帮助开发者有效地处理程序运行时出现的各种意外情况,避免程序崩溃,提高程序的健壮性和用户体验。在C#中,异常处理机制主要包括抛出异常和捕获异常两个方面。 ...

    C_与C++中的异常处理.pdf

    这部分探讨了这两种异常处理方式之间的相互作用和如何在同一个程序中协调使用它们。 综上所述,异常处理是C与C++程序设计中的一个重要主题。通过深入理解异常的生命阶段、异常处理的不同方法和技术,以及如何编写...

    java 异常处理练习

    在Java编程语言中,异常处理是一项关键的技能,它帮助开发者编写健壮的代码,能够优雅地处理程序运行时可能出现的错误。在这个“java 异常处理练习”中,我们将深入探讨`try-catch-finally`、`throw`和`throws`...

    Java异常处理与输入输出.ppt

    Java 异常处理与输入输出 Java 异常处理是 Java 语言中的一种运行错误处理机制,用于处理程序中的运行错误,以确保程序的安全性和稳定性。Java 中定义了许多异常类,每个异常类代表一种运行错误,类中包含了该运行...

    实时操作系统Vxworks下的异常处理

    Vxworks在系统启动时会自动安装一个默认的异常处理函数。当异常发生时,系统会自动调用这个处理函数。默认处理通常包括记录错误信息和可能的重启操作。 **2.2 用户自定义异常处理** 除了默认处理外,用户还可以...

    ARM中异常中断处理

    在 ARM 体系中,异常中断处理是通过一个特殊的机制来实现的。当处理器执行一条指令时,如果发生了异常中断,处理器将跳转到相应的异常中断处理程序处执行。在执行完异常中断处理程序后,程序将返回到发生中断的指令...

    ADS异常处理ADS异常处理

    * 异常处理流程包括异常检测、异常处理和异常返回三个阶段。 * 向量表是 ARM 处理器中的异常向量表,用于存储异常处理程序的入口地址。 * 寄存器使用是异常处理中的重要部分,包括 CPSR、SPSR_、LR_<mode> 等寄存器...

    异常处理的陋习

    4. **处理逻辑**:异常处理应该包含修复错误、通知用户或者恢复系统到一个安全状态的逻辑。仅仅打印堆栈跟踪并不解决问题。 5. **最后的catch块**:可以有一个`catch(Exception e)`作为最后的手段,用于捕获所有未...

Global site tag (gtag.js) - Google Analytics