Spring的事務實現(xiàn)采用基于AOP的攔截器來實現(xiàn),如果沒有在事務配置的時候注明回滾的checked exception,那么只有在發(fā)生了unchecked exception的時候,才會進行事務回滾。因此在DAO層和service層,最好拋出unckecked exception,畢竟對于數(shù)據(jù)庫操作,使用unckecked exception更加合適,這個方面的例子hibernate就是一個,在hibernate2中,HibernateException還是checked exceptions,但是到了hibernate3中就成了unchecked exceptions,因為對于數(shù)據(jù)庫操作來說,一旦出現(xiàn)異常,就是比較嚴重的錯誤,而且在client端基本上是無能為力的,所以使用unchecked exceptions更加合適。

另外,在DAOservice層的代碼中,除非是為了異常的轉(zhuǎn)化、重新拋出,否則不要捕捉和處理異常,否則AOP在攔截的時候就不能捕捉到異常,也就不能正確執(zhí)行回滾。這一點通常很容易被忽視,只有在明白了spring的事務處理機制后,才能領會到。

對于hibernate的異常,spring會包裝hibernateupckecked hibernateExceptionDAOAccessException,并且拋出,在事務管理層,一旦接收到DAOAccessException就會觸發(fā)事務的回滾,同時該異常會繼續(xù)向上層拋出,供上層進一步處理,比如在UI層向用戶反饋錯誤信息等。

下面的來自spring參考手冊的例子說明了spring的事務和異常的關(guān)系,為了更好地說明問題,我修改了部分代碼:
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("執(zhí)行事務操作時發(fā)生異常",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(
"事務操作出現(xiàn)異常");
        }
        
        
    }
}

這里,當Boot對象調(diào)用FooService來進行事務操作時,由于在事務操作時拋出了unchecked exception,被SpringAOP事務處理模塊攔截,觸發(fā)了事務的回滾,同時最終在控制臺上打出了“事務操作出現(xiàn)異常”,說明spring在觸發(fā)了數(shù)據(jù)庫回滾的同時又重新拋出了該異常。

為了更好地看到spring事務攔截的過程,建議將日志模式調(diào)至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>

結(jié)論

在spring的事務管理環(huán)境下,使用unckecked exception可以極大地簡化異常的處理,只需要在事務層聲明可能拋出的異常(這里的異常可以是自定義的unckecked exception體系),在所有的中間層都只是需要簡單throws即可,不需要捕捉和處理,直接到最高層,比如UI層再進行異常的捕捉和處理