浏览 1937 次
锁定老帖子 主题:一个特殊的异常处理
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2008-12-13
最后修改:2008-12-14
文:袁光东 一、业务需求说明 前段时间接到一个关于援助卡的需求。这个需求比较特殊的是援助卡的卡号是由单证系统进行管理的。 可能用银行卡来说明比较清楚一些。 银行卡是由银行统一制作出来的,每一个银行卡号都是事先已经编制了的。银行有一个单证部门来管理这些印刷的银行卡。各个营业点,会向单证部门领取银行卡。此时单证系统的银行卡为领取状态。 当一个储户去银行开户时,银行职员会为储户选择一张银行卡。这张银行卡的卡号就成了储户的帐号。 计算机系统完成开户处理需要有三个步骤: 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方法。该方法是看一个异常是属于某个类型的异常。从当前开始比较,如果不匹配就拿该异常类的父类去比较。 这样问题就获得了圆满的解决。即保证了事务的原子性,又加强了程序的健壮性。 总结: 在实现业务方法,进行多种操作或调用外部接口时,一定要考虑事务原子性属性。有些时候是要你手动来保证的。 并于异常的处理,有时不是简单的抛出异常草草了事,能够提供给用户重试机会的,就需要给用户重试的机会。提高程序的稳定性和健壮性。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2008-12-14
我觉得 你说的事务原子性 其实应该在设计的时候就进行分解,你的问题不是出在异常怎么处理上而是业务分解的不合理。对业务颗粒度的分解把握不恰当,你的异常处理其实是在补救设计上的问题而已。
|
|
返回顶楼 | |
发表时间:2008-12-14
fjlyxx 写道 我觉得 你说的事务原子性 其实应该在设计的时候就进行分解,你的问题不是出在异常怎么处理上而是业务分解的不合理。对业务颗粒度的分解把握不恰当,你的异常处理其实是在补救设计上的问题而已。 如果你遇到大型系统,就会深入感受了。一些方法是由其它项目组提供的。并且你不能自己去写一套。因为这个方法的逻辑是由其它项目组来维护。同时这个方法并是以procedure方式来实现的。你只能调用这个procedure. 这不是补救的问题。 |
|
返回顶楼 | |
发表时间:2008-12-14
klyuan 写道 fjlyxx 写道 我觉得 你说的事务原子性 其实应该在设计的时候就进行分解,你的问题不是出在异常怎么处理上而是业务分解的不合理。对业务颗粒度的分解把握不恰当,你的异常处理其实是在补救设计上的问题而已。 如果你遇到大型系统,就会深入感受了。一些方法是由其它项目组提供的。并且你不能自己去写一套。因为这个方法的逻辑是由其它项目组来维护。同时这个方法并是以procedure方式来实现的。你只能调用这个procedure. 这不是补救的问题。 呵呵 ,怎么不考虑发布成一个服务了,其他业务提供的方法 最好能发布成一个服务。靠procedure本来就是一个不明智的做法,安全性和可维护性,还有访问标准问题都得不到解决。在大型项目中这个更容易出问题。如果你这个项目在SOA平台上 也许你要做的只是写一个简单的BPEL脚本。 |
|
返回顶楼 | |
发表时间:2008-12-14
fjlyxx 写道 klyuan 写道 fjlyxx 写道 我觉得 你说的事务原子性 其实应该在设计的时候就进行分解,你的问题不是出在异常怎么处理上而是业务分解的不合理。对业务颗粒度的分解把握不恰当,你的异常处理其实是在补救设计上的问题而已。 如果你遇到大型系统,就会深入感受了。一些方法是由其它项目组提供的。并且你不能自己去写一套。因为这个方法的逻辑是由其它项目组来维护。同时这个方法并是以procedure方式来实现的。你只能调用这个procedure. 这不是补救的问题。 呵呵 ,怎么不考虑发布成一个服务了,其他业务提供的方法 最好能发布成一个服务。靠procedure本来就是一个不明智的做法,安全性和可维护性,还有访问标准问题都得不到解决。在大型项目中这个更容易出问题。如果你这个项目在SOA平台上 也许你要做的只是写一个简单的BPEL脚本。 soa平台并不能够解决掉这个问题。从性能上来说,procedure更优。 另外公司的访问规范是不可以轻易违反的。 |
|
返回顶楼 | |
发表时间:2008-12-14
如果在没有业务限制的情况下,我觉得不应该用procedure。procedure更优这句是有前提的,在压力小的系统中是这样的。如果你有集群那么你是否把这个压力都交给数据库了。这是一个陷阱。
基于规范访问调用的平台会比你用procedure更优,当然你的应用现状这样也是没有办法的。 |
|
返回顶楼 | |