java中从Spring、Hibernate和Struts框架的action、service和dao三层结构异常处理体系设计

java中从Spring、Hibernate和Struts框架的action、service和dao三层结构异常处理体系设计  



Spring的事务实现采用基于AOP的拦截器来实现,如果没有在事务配置的时候注明回滚的checked exception,那么只有在发生了unchecked exception的时候,才会进行事务回滚。因此在DAO层和service层,最好抛出unckecked exception,毕竟对于数据库操作,使用unckecked exception更加合适,这个方面的例子hibernate就是一个,在hibernate2中,HibernateException还是checked exceptions,但是到了hibernate3中就成了unchecked exceptions,因为对于数据库操作来说,一旦出现异常,就是比较严重的错误,而且在client端基本上是无能为力的,所以使用unchecked exceptions更加合适。

另外,在DAOservice层的代码中,除非是为了异常的转化、重新抛出,否则不要捕捉和处理异常,否则AOP在拦截的时候就不能捕捉到异常,也就不能正确执行回滚。这一点通常很容易被忽视,只有在明白了spring的事务处理机制后,才能领会到。

对于hibernate的异常,spring会包装hibernateupckecked hibernateExceptionDAOAccessException,并且抛出,在事务管理层,一旦接收到DAOAccessException就会触发事务的回滚,同时该异常会继续向上层抛出,供上层进一步处理,比如在UI层向用户反馈错误信息等。


1异常的作用 
?业务处理流程和错误处理流程分离,使代码更简洁,易懂。 
?便于程序员调试和排错。 
?异常捕获,向用户提供友好信息。 
...... 


2异常使用要点 
?  重新抛出的异常必须保留原来的异常,即throw new NewException("message", e); 而不能写成throw new NewException("message")。 
?  在所有异常被捕获且没有重新抛出的地方必须写日志。 
?  如果属于正常异常的空异常处理块必须注释说明原因,否则不允许空的catch块。 
针对第一点举个例子:  
Java代码 
  1. class NewException extends Exception {  
  2. }  
  3. public class ExceptionTest {  
  4.     public void test() {  
  5.         try {  
  6.             throw new NewException();  
  7.         } catch (NewException e) {  
  8.             //throw new RuntimeException("检测异常转为非检测异常。", e);  
  9.             throw new RuntimeException("检测异常转为非检测异常。");  
  10.         }  
  11.     }  
  12.   
  13.     public static void main(String[] args) {  
  14.         ExceptionTest et = new ExceptionTest();  
  15.         et.test();  
  16.     }  
  17. }  
  18.   
  19. /* 
  20. throw new RuntimeException("检测异常转为非检测异常。", e)结果: 
  21. Exception in thread "main" java.lang.RuntimeException: 检测异常转为非检测异常。 
  22.     at cn.yang.ExceptionTest.test(ExceptionTest.java:12) 
  23.     at cn.yang.ExceptionTest.main(ExceptionTest.java:18) 
  24. Caused by: cn.yang.NewException 
  25.     at cn.yang.ExceptionTest.test(ExceptionTest.java:10) 
  26.     ... 1 more 
  27. */  
  28. /* 
  29. throw new RuntimeException("检测异常转为非检测异常。")结果: 
  30. Exception in thread "main" java.lang.RuntimeException: 检测异常转为非检测异常。 
  31.     at cn.yang.ExceptionTest.test(ExceptionTest.java:12) 
  32.     at cn.yang.ExceptionTest.main(ExceptionTest.java:18) 
  33. */  




3三层结构 
    在action、service和dao三层结构中处理异常,一般是dao层抛出HibernateException(不用捕获,为runtimeException),在service层抛出自定义的业务异常ServiceException,最后在action中统一捕获自定义的业务异常ServiceException通知用户(可以为所有的action定义一个拦截器ExceptionInterceptor捕获这个自定义的业务异常,这样就不必每个action里都写try catch)。 
    需要注意的是在service中抛出业务异常最好继承RuntimeException。因为事务一般放在service层,如果抛出的是checked异常,事务不会回滚(可以配置),这是spring的特性。spring抛出的是unchecked异常,Hibernate3也从检查性异常转为非检查性异常(HibernateException),因此service层调用dao层抛出HibernateException异常时,service层定义的事务将会回滚。 

下面的来自 spring 参考手册的例子说明了 spring 的事务和异常的关系,为了更好地说明问题,我修改了部分代码:
package  x.y.service;
import  org.springframework.dao.DataAccessException;
import  org.springframework.dao.PermissionDeniedDataAccessException;

public   class  DefaultFooService  implements  FooService {
    
public  Foo getFoo(String fooName) {
        
throw   new  UnsupportedOperationException();
    }

    
public  Foo getFoo(String fooName, String barName) {
        
throw   new  UnsupportedOperationException();
    }

    
public   void  insertFoo(Foo foo) throws  DataAccessException {
        
throw   new  PermissionDeniedDataAccessException( " 执行事务操作时发生异常 " , new  UnsupportedOperationException());
    }

    
public   void  updateFoo(Foo foo) {
        
throw   new  UnsupportedOperationException();
    }
}

package  x.y.service;
import  org.springframework.context.ApplicationContext;
import  org.springframework.context.support.ClassPathXmlApplicationContext;
import  org.springframework.dao.DataAccessException;

public   final   class  Boot {

    
public   static   void  main( final  String[] args)  throws  Exception {
        ApplicationContext ctx 
=   new  ClassPathXmlApplicationContext(
                
" applicationContext.xml " ,Boot. class );
        FooService fooService 
=  (FooService) ctx.getBean( " fooService " );
        
try  {
            fooService.insertFoo(
new  Foo());
        } 
catch  (DataAccessException e) {
            System.out.println(
" 事务操作出现异常 " );
        }
        
        
    }
}

这里,当Boot对象调用FooService来进行事务操作时,由于在事务操作时抛出了unchecked exception,被SpringAOP事务处理模块拦截,触发了事务的回滚,同时最终在控制台上打出了“事务操作出现异常”,说明spring在触发了数据库回滚的同时又重新抛出了该异常。 

为了更好地看到 spring 事务拦截的过程,建议将日志模式调至 debug 模式
package  x.y.service;

public   class  Foo {
}

package  x.y.service;

import  org.springframework.dao.DataAccessException;

public   interface  FooService {
    
    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    
void  insertFoo(Foo foo) throws  DataAccessException;

    
void  updateFoo(Foo foo) throws  DataAccessException;
}

<? xml version="1.0" encoding="UTF-8" ?>
< beans  xmlns ="http://www.springframework.org/schema/beans"
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop
="http://www.springframework.org/schema/aop"
    xmlns:tx
="http://www.springframework.org/schema/tx"
    xsi:schemaLocation
="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
>



    
<!--  this is the service object that we want to make transactional  -->
    
< bean  id ="fooService"  class ="x.y.service.DefaultFooService"   />

    
<!--  the transactional advice (i.e. what 'happens'; see the <aop:advisor/> bean below)  -->
    
< tx:advice  id ="txAdvice"  transaction-manager ="txManager" >
        
<!--  the transactional semanticsjava中从Spring、Hibernate和action三层结构异常处理体系设计 - 天涯草 - 天涯草  -->
        
< tx:attributes >
            
<!--  all methods starting with 'get' are read-only  -->
            
< tx:method  name ="get*"  read-only ="true"   />

            
<!--  other methods use the default transaction settings (see below)  -->
            
< tx:method  name ="*"   />
        
</ tx:attributes >
    
</ tx:advice >

    
<!--  ensure that the above transactional advice runs for any execution
        of an operation defined by the FooService interface 
-->
    
< aop:config >
        
< aop:pointcut  id ="fooServiceOperation"
            expression
="execution(* x.y.service.FooService.*(..))"   />
        
< aop:advisor  advice-ref ="txAdvice"
            pointcut-ref
="fooServiceOperation"   />
    
</ aop:config >

    
<!--  don't forget the DataSource  -->
    
< bean  id ="dataSource"
        class
="org.apache.commons.dbcp.BasicDataSource"
        destroy-method
="close" >
        
< property  name ="driverClassName"  value ="org.h2.Driver"   />
        
< property  name ="url"
            value
="jdbc:h2:tcp://localhost/D:/try/data/sample;IFEXISTS=TRUE"   />
        
< property  name ="username"  value ="sa"   />
        
< property  name ="password"  value ="123456"   />
    
</ bean >

    
<!--  similarly, don't forget the PlatformTransactionManager  -->
    
< bean  id ="txManager"
        class
="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
        
< property  name ="dataSource"  ref ="dataSource"   />
    
</ bean >
    
<!--  other <bean/> definitions here  -->
</ beans >

结论

在spring的事务管理环境下,使用unckecked exception可以极大地简化异常的处理,只需要在事务层声明可能抛出的异常(这里的异常可以是自定义的unckecked exception体系),在所有的中间层都只是需要简单throws即可,不需要捕捉和处理,直接到最高层,比如UI层再进行异常的捕捉和处理

关于在应用程序中如何设计合理的异常体系,在《深入浅出Hibernate》这本书中有一大段详细的论述,另外一些JAVA界的高人也先后发表过专门的文章来讨论这个问题,书中也给出的文章的链接。 

其实,通过Hibernate和Spring中对异常处理的态度,就可以看出一些好的设计原则。在Hibernate2的时候,调用Hibernate的API时必须处理一个CheckedException,这和使用JDBC时是一样的,Hibernate的作者在后来的一封Email中也承认多少是受了JDBC的影响。但是面对这种底层的异常,我们能做些什么呢?继续抛上去或者捕获后什么都不做。这样的做法只能使程序中充满了重复的没有太大意义的try...catch...代码,而其功能和一个UncheckedException没用太大差别。所以Spring包装了Hibernate后抛出了一个UnckeckedException。后来Hibernate也意识到抛出一个UncheckedException是一种更合适的做法,所以在Hibernate3中不再抛出CheckedException了。 

今天又看到了一个Spring的特性,更加说明了Spring在异常处理方面的思路和态度: 

默认的情况下,spring只有当unchecked exception被抛出时,才rollback事务。 
Java代码 
  1. <property name="transactionAttributes">   
  2.     <props>   
  3.             <prop key="insert*">PROPAGATION_REQUIRED,- MyCheckedException,+MyUnCheckedException</prop>   
  4.             <prop key="update*">PROPAGATION_REQUIRED</prop>   
  5.             <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>   
  6.         </props>   
  7. </property>   
-MyCheckedException 指定如果方法抛出MyCheckedException或它的子类,事务将 会自动回滚。 
+MyUnCheckedException 指定如果方法抛出MyUnCheckedException或它的子类,事务仍将会提交。 

可我们的项目中是怎么做的呢? 

几乎在每一层的每一个方法中都try...catch一下,然后在catch代码块中抛出一个OAException,这个OAException是New出来的而且还是一个CheckedException,包含了一句简单的异常信息,如“数据访问出现错误……”。这样做的后果有两个,一是又回到了Hibernate2的那种异常处理方式,使代码变的复杂了,有时候只有一两行的代码就可以搞定的现在要写个十多行的异常处理代码。二是丢掉了原始的异常信息,这一点就更严重了,由于OAException是New出来的,所以只包含了简单的提示信息,而真正出现异常的原始信息却丢掉了。另外,从上面Spring事务的回滚机制来看,如果采用默认配置,事务都会变的形同虚设了。 

这种异常处理体系对程序员是一种痛苦,对程序设计来说是一种缺憾! 

查资料时看见有前辈总结的spring的异常设计,摘抄如下: 
一个统一的异常层次结构对于提供服务抽象是必需的。 最重要的就是org.springframework.dao.DataAccessException以及其子类了。 需要强调的是Spring的异常机制重点在于应用编程模型。与SqlException和其他数据存取API不同的是: Spring的异常机制是为了让开发者使用最少, 最清晰的代码。DataAccessException和其他底层异常都是非检查性异常(unchecked exception)。 spring的原则之一就是基层异常就应该是非检查性异常. 原因如下: 
1. 基层异常通常来说是不可恢复的。 
2. 检查性异常将会降低异常层次结构的价值.如果底层异常是检查性的, 那么就需要在所有地方添加catch语句进行捕获。 
3.try/catch代码块冗长混乱, 而且不增加多少价值。 
使用检查异常理论上很好, 但是实际上好象并不如此。 
Hibernate3也将从检查性异常转为非检查性异常。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值