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

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. public static void main(String[] args) {
  13. ExceptionTest et = new ExceptionTest();
  14. et.test();
  15. }
  16. }
  17. /*
  18. throw new RuntimeException("检测异常转为非检测异常。", e)结果:
  19. Exception in thread "main" java.lang.RuntimeException: 检测异常转为非检测异常。
  20. at cn.yang.ExceptionTest.test(ExceptionTest.java:12)
  21. at cn.yang.ExceptionTest.main(ExceptionTest.java:18)
  22. Caused by: cn.yang.NewException
  23. at cn.yang.ExceptionTest.test(ExceptionTest.java:10)
  24. ... 1 more
  25. */
  26. /*
  27. throw new RuntimeException("检测异常转为非检测异常。")结果:
  28. Exception in thread "main" java.lang.RuntimeException: 检测异常转为非检测异常。
  29. at cn.yang.ExceptionTest.test(ExceptionTest.java:12)
  30. at cn.yang.ExceptionTest.main(ExceptionTest.java:18)
  31. */

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,被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层再进行异常的捕捉和处理

关于在应用程序中如何设计合理的异常体系,在《深入浅出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也将从检查性异常转为非检查性异常。

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

  1. 第64节:Java中的Spring Boot 2.0简介笔记

    Java中的Spring Boot 2.0简介笔记 spring boot简介 依赖java8的运行环境 多模块项目 打包和运行 spring boot是由spring framework构建的,sp ...

  2. 第63节:Java中的Spring MVC简介笔记

    前言 感谢! 承蒙关照~ Java中的Spring MVC简介笔记 MVC简介 Spring MVC 基本概念 Spring MVC 项目搭建 maven 使用Spring MVC进行开发 实现数据绑 ...

  3. Java Web开发框架Spring+Hibernate整合效果介绍(附源码)

    最近花了一些时间整合了一个SpringMVC+springAOP+spring security+Hibernate的一套框架,之前只专注于.NET的软件架构设计,并没有接触过Java EE,好在有经 ...

  4. Java Web开发框架Spring+Hibernate整合效果介绍(附源码)(已过期,有更好的)

    最近花了一些时间整合了一个SpringMVC+springAOP+spring security+Hibernate的一套框架,之前只专注于.NET的软件架构设计,并没有接触过Java EE,好在有经 ...

  5. hibernate与struts框架实现增删改查

    这里配置hibernate与struts不再过多赘述,配置搭建前文已经详细讲解,配置如下: hibernate.hbm.xml配置: <?xml version="1.0" ...

  6. 来,带你鸟瞰 Java 中4款常用的并发框架!

    1. 为什么要写这篇文章 几年前 NoSQL 开始流行的时候,像其他团队一样,我们的团队也热衷于令人兴奋的新东西,并且计划替换一个应用程序的数据库. 但是,当深入实现细节时,我们想起了一位智者曾经说过 ...

  7. java中的最重要的 集合框架

    java.util这个重要的包包含大量的类和接口,支持很多的功能.例如,java.util具有能产生伪随机数的类,还包括可以管理日期和时间.观察事件.操作位集合.标记字符串.处理格式化数据等的类.ja ...

  8. 学习Struts框架系列(三):声明式异常处理

    在Struts1.X的版本中加入了对异常的处理Exception Handler,有了它我们可以不使用try/catch捕获异常,一旦出现了我们已经定义的异常,那么就会转到相应的页面,并且携带异常信息 ...

  9. Java中常见方法详解合集(方法的定义及语法结构)

    Java的方法定义 1.方法的定义 方法是控制对象的动作行为方式与准则,在Java中方法位于类体下又有另一种含义. 普通的方法在类中称为"实例方法",因为方法的调用需要创建对象,而 ...

随机推荐

  1. Qt数据库操作(qt-win-commercial-src-4.3.1,VC6,Oracle,SQL Server)

    qt-win-commercial-src-4.3.1.qt-x11-commercial-src-4.3.1Microsoft Visual C++ 6.0.KDevelop 3.5.0Window ...

  2. SQL Server存储机制二

    http://blog.csdn.net/ltylove2007/article/details/21084585 http://www.cnblogs.com/anding/p/3254674.ht ...

  3. php之curl实现http与https请求的方法

    原文地址:http://m.jb51.net/show/56492   这篇文章主要介绍了php之curl实现http与https请求的方法,分别讲述了PHP访问http网页与访问https网页的实例 ...

  4. Solr搜索基础

    本例我们使用类库和代码均来自: http://www.cnblogs.com/TerryLiang/archive/2011/04/17/2018962.html 使用C#来模拟搜索.索引建立.删除. ...

  5. [LeetCode]题解(python):049-Groups Anagrams

    题目来源 https://leetcode.com/problems/anagrams/ Given an array of strings, group anagrams together. For ...

  6. sql server 2008查询窗口怎么显示行数

    工具->选项

  7. imx6 kernel clock

    前段时间查看了uboot的时钟,kernel的也稍微了解了下,记录于此,以后再来补充完善. board-mx6q_sabresd.c MACHINE_START(MX6Q_SABRESD, " ...

  8. Redis实现分布式锁

    http://redis.io/topics/distlock 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但 ...

  9. marathon参考(11):ports端口设置(转)

    Ports marathon中应用的端口配置可能被混淆,并有一个悬而未决的问题,需要重新设计 ports API.这个页面试图更清楚地解释它们是如何工作的. 定义 containerPort:在容器内 ...

  10. webstorm注册

    选择“license server” 输入:http://114.215.133.70:41017(适用ws2016.2.2) 2.3选择activation code注册 43B4A73YYJ-ey ...