`
klyuan
  • 浏览: 182498 次
  • 性别: 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  
我觉得  你说的事务原子性 其实应该在设计的时候就进行分解,你的问题不是出在异常怎么处理上而是业务分解的不合理。对业务颗粒度的分解把握不恰当,你的异常处理其实是在补救设计上的问题而已。

相关推荐

    java异常处理

    异常处理,是编程语言或计算机硬件里的一种机制,用于处理软件或信息系统中出现的异常状况(即超出程序正常执行流程的某些特殊条件)。 通过异常处理,我们可以对用户在程序中的非法输入进行控制和提示,以防程序...

    事故及异常处理规程

    事故及异常处理规程 感謝您採用台達 DVP-EC3 系列可程式控制器。DVP-EC3 系列目前提供 10 ~ 60 點數的 主機。為了確保能夠正確地安裝及操作可程式控制器,請在裝機之前,詳細閱讀本使用手 冊,並請妥善保存及交由該...

    C++ 异常处理 catch(…)介绍

    如果要想使一个catch block能抓获多种数据类型的异常对象的话,怎么办?C++标准中定义了一种特殊的catch用法,那就是” catch(…)”。 感性认识 1、catch(…)到底是一个什么样的东东,先来个感性认识吧!看例子先: ...

    JAVA实验十一 异常处理与集合类

    1、 为SavingsAccount类和CheckingAccout类的取款行为分别定义异常类NotEnoughBalanceException和OverdraftLimitExceededException,当...取款超过余额或者当CheckingAccount类产生透支超过最大额度时产生异常...

    wince 结构化异常处理

    由编译器实现的处理结束以及异常的特殊功能。 目的: 是程序更加健壮

    详细分析C++ 异常处理

    异常是程序在执行期间产生的问题。C++ 异常是指在程序运行时发生的特殊情况,比如尝试除以零的操作。 异常提供了一种转移程序...如果有一个块抛出一个异常,捕获异常的方法会使用 try 和 catch 关键字。try 块中放置可

    python的中异常处理机制

    什么是异常处理 定义:异常处理就是我们在写Python时,经常看到的报错信息,例如;NameError TypeError ValueError等,这些都是异常。 异常是一个事件,改事件会在程序执行过程中发生,影响程序的正常执行,一般情况...

    Mondrian的异常处理小结

    由于要做多维报表的展现,需要使用OLAP的引擎Mondrian,于是最近一直在学习MDX查询语言,在做Demo时遇到了很多bug,我认为这些异常对于初学MDX查询语言的朋友来说很常见,一般都会遇到这些bug,这里我将自己遇到的...

    10道Java常见异常面试题含答案(很全)

    Java异常处理是Java编程中一个非常重要的部分,它允许程序在遇到错误或异常情况时采取适当的行动,而不是简单地崩溃。 在Java中,异常(Exception)是一种特殊的对象,表示程序运行过程中出现的问题。Java提供了很...

    JAVA异常基础入门

    是一类特殊的异常,如被 0 除、数组下标超范围等,其产生比较频繁,处理麻烦,如果显式的声明或捕获将会对程序可读性和运行效率影响很大。因此由系统自动检测并将它们交给缺省的异常处理程序(用户可不必对其处理)...

    【Java】异常类体系及异常处理

    文章目录1. 异常概念2. 异常分类体系3. 异常的抛出方式:throw & throws & 默认4....另外,调用这个方法的其他代码也无法继续执行,异常处理机制会将代码执行交给异常处理器。 2. 异常分类体系 Thr

    学习Java,你需要知道这些——Java异常

    文章目录异常处理的概念异常的基本概念Java异常处理机制的优点错误的分类异常的分类预定义的一些常见异常异常的处理抛出异常捕获异常的语法生成异常对象声明自己的异常类 异常处理的概念 在程序在运行的时候可能会...

    Python程序设计之异常处理(2)

    1.处理异常的两种特殊方法: ①断言: 语句格式:assert exception[,reason] 解释:当exception为真时,什么都不做,如果表达式为假就抛出异常; 注意:assert一般用于对某个时刻必须满足的条件进行验证,__debug__为...

    你应该知道的.NET错误与异常处理机制

    在C#中当出现某个特殊的异常错误条件时,就会创建抛出一个异常对象,这个对象包含有助于跟踪问题的信息。.Net提供了许多预定义的异常类,我们下面看看一些常见特别的异常类吧(异常类太多了,这里就介绍几个常见的)...

    python学习笔记(十五)异常处理

    python学习笔记(十五)异常处理 ...异常处理可以认为是一种特殊的流程控制语句 异常处理可以提高代码的健壮性 语法 try: print('正常代码') a = 10 / 0 print('执行结束') except Exception as e: # Except

    java异常机制总结

    关键字:throw throw是一种控制程序流程的特殊方法而已。没有相应的catch的话,可以中止当前的方法继续执行。 关键字:throws ...他们一个负责发现异常关系,一个负责异常捕获。很辛苦的一对难兄难弟哦,:)

    Java异常处理中的一些特殊情况举例

    主要介绍了Java异常处理中的一些特殊情况举例,分别是只用try和finally不用catch,以及finally语句不被执行的情况,需要的朋友可以参考下

    JSTL详细标签库介绍

    异常处理程序:能够让系统在出现异常的情况下恢复过来的程序使用异常处理情况:异常诊断与异常处理不在同一个位置下时使用异常处理程序(若用户一直通过键盘与程序通话,那么就不能使用处理键盘输入处理)使用异常的...

    SQL Server自定义异常raiserror使用示例

    在使用SQL Server存储过程或者触发器时,通常会使用自定义异常来处理一些特殊逻辑。例如游标的销毁,事务的回滚。接下来将会详细的介绍SQL Server自定义异常的使用。 使用“raiserror”来抛出自定义异常。如下代码:...

    C++之try catch 异常处理入门实例

    因为运行程序时还可能会遇到异常,例如当我们设计一个为用户计算除法的程序时,用户很有可能会将除数输入为零,又例如当我们需要打开一个文件的时候确发现该文件已经被删除了……类似的这种情况很有很多,针对这些...

Global site tag (gtag.js) - Google Analytics