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层向用户反馈错误信息等。

下面的来自 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 semantics  -->
        
< 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层再进行异常的捕捉和处理