Spring的事务实现采用基于AOP的拦截器来实现,如果没有在事务配置的时候注明回滚的checked
exception,那么只有在发生了unchecked
exception的时候,才会进行事务回滚。因此在DAO层和service层,最好抛出unckecked exception,毕竟对于数据库操作,使用unckecked exception更加合适,这个方面的例子hibernate就是一个,在hibernate2中,HibernateException还是checked exceptions,但是到了hibernate3中就成了unchecked exceptions,因为对于数据库操作来说,一旦出现异常,就是比较严重的错误,而且在client端基本上是无能为力的,所以使用unchecked exceptions更加合适。
另外,在DAO和service层的代码中,除非是为了异常的转化、重新抛出,否则不要捕捉和处理异常,否则AOP在拦截的时候就不能捕捉到异常,也就不能正确执行回滚。这一点通常很容易被忽视,只有在明白了spring的事务处理机制后,才能领会到。
对于hibernate的异常,spring会包装hibernate的upckecked hibernateException到DAOAccessException,并且抛出,在事务管理层,一旦接收到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,被Spring的AOP事务处理模块拦截,触发了事务的回滚,同时最终在控制台上打出了“事务操作出现异常”,说明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层再进行异常的捕捉和处理