Spring @Transactional 事务回滚

Spring @Transactional 事务回滚

一、问题描述:

service层,一个method中包含了多条对数据库的操作。若sql1成功,sql2出错,我们希望这个method中的所有对数据的操作都会回滚,即sql1也是失败的情况。

//此方法:前边的数据库操作成功,发生数据库操作之后,后边的所有数据库操作将失败。
public method(){
  service.save1(User1);//操作sql1
  service.save2(User2);//操作sql2   //save2执行失败,User1已经被保存到数据库中,希望被回滚
  .......
  service.save9(User9);//操作sql9
}

二、JAVA异常:

1.异常简介

Java异常是Java提供的一种识别及响应错误的一致性机制。

Java异常机制可以使正常业务代码和异常处理代码分离,提高程序健壮性。

异常类型表示了什么错误被抛出;异常堆栈跟踪表示了代码中某个类的某一行出错;异常信息表示了错误的原因。

Java异常机制经常用到以下几个关键字:try、catch、finally、throw、throws

1)try:用于监听。try语句块内放入被监听的代码,当代码块发生异常,异常会被抛出。

public class Main {
    public static void main(String[] args) {
        try {
            int i = 10/0;         //此处发生异常,之后的代码都不会被执行。
            System.out.println("i="+i);
        } catch (ArithmeticException e) {
            System.out.println("Caught Exception");
            System.out.println("e.getMessage(): " + e.getMessage());
            System.out.println("e.toString(): " + e.toString());
            System.out.println("e.printStackTrace():");
            e.printStackTrace();
        }
    }
}

2)catch:用于捕获try语句块中发生的异常。

“int i = 10/0; ”发生异常之后,“System.out.println(“i=”+i);”并没有执行

Caught Exception
java.lang.ArithmeticException: / by zero
e.getMessage(): / by zero
    at Main.main(Main.java:5)   //说明第五行出现空指针异常
e.toString(): java.lang.ArithmeticException: / by zero
e.printStackTrace():  

3)finally:该语句块总会被执行。

它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。

public class Main {
    public static void main(String[] args) {
        try {
            int i = 10/0;
            System.out.println("i="+i);
        } catch (ArithmeticException e) {
            e.printStackTrace();
            throw e;
        }finally {
            System.out.println("run finally");
        }
    }
}


run finally     //先执行finally块
java.lang.ArithmeticException: / by zero   //然后打印异常
    at Main.main(Main.java:4)
Exception in thread "main" java.lang.ArithmeticException: / by zero  //然后执行catch,抛出异常
    at Main.main(Main.java:4)

4)throw:抛出异常。

5)throws:声明抛出的异常。

2.异常架构

image.png

从上述图示可以看到,

Thorwable类(表示可抛出)是所有异常和错误的超类,两个直接子类为Error和Exception,分别表示错误和异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常, 这两种异常有很大的区别,也称之为不检查异常(Unchecked Exception)和检查异常(Checked Exception)。下面将详细讲述这些异常之间的区别与联系:

1)Error与Exception

Error是程序无法处理的错误,它是由JVM产生和抛出的,比如OutOfMemoryError、ThreadDeath等。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
Exception是程序本身可以处理的异常,这种异常分两大类运行时异常和非运行时异常。程序中应当尽可能去处理这些异常。

2)运行时异常和非运行时异常

运行时异常都是RuntimeException类及其子类异常,如NullPointerException、IndexOutOfBoundsException等,这些异常是unchecked异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。
非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

比如:代码校验时就可以抛出运行时异常,然后回滚事务。

if (d.getModifyReason() == "" || "".equals(d.getModifyReason()) || d.getModifyReason() == null) {
    message = "资质信息Tab页中的修改原因不可为空;";
    throw new RuntimeException(message);
}
3)checked异常和unchecked异常

checked异常:继承自java.lang.Exception(java.lang.RuntimeException除外)。

表示无效,不是程序中可以预测的。比如无效的用户输入,文件不存在,网络或者数据库链接错误。这些都是外在的原因,都不是程序内部可以控制的。
必须在代码中显式地处理。比如try-catch块处理,或者给所在的方法加上throws说明,将异常抛到调用栈的上一层。

unchecked异常:继承自java.lang.RuntimeException(而java.lang.RuntimeException继承自java.lang.Exception)。

表示错误,程序的逻辑错误。是RuntimeException的子类,比如IllegalArgumentException, NullPointerException和IllegalStateException。不需要在代码中显式地捕获unchecked异常做处理。

三、事务配置

首先配置Spring的配置文件。

在spring的配置文件中,如果数据源的defaultAutoCommit设置为True了,那么方法中如果自己捕获了异常事务是不会回滚的,如果没有自己捕获异常则事务会回滚。

<!-- Jpa 事务配置 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> 
    <property name="xxx" value="xxx"/> 
    <property name="xxx" value="xxx"/>
     ....
    <property name="defaultAutoCommit" value="true" /> 
</bean>
1)如果没有捕获异常,事务会回滚。

若save2出错,此时save1操作会回滚。

@Transactional(rollbackOn = { Exception.class })  
public void method() throws Exception {  
     service.save1(User1);  
     service.save2(User2);
} 
2)如果捕获了异常,事务不会回滚。

若save2出错,save1不会回滚。

@Transactional(rollbackOn = { Exception.class })  
public void method() throws Exception { 
    try{ 
       service.save1(User1);  
       service.save2(User2);
     }catch(Exception e){
        e.printStackTrace(); 
     }          
} 
3) throw new RuntimeException(); 回滚事务

默认的Spring事务是:异常没有被捕获,然后回滚事务。

spring aop 异常捕获然后回滚原理:被拦截的方法需显式抛出异常,并不经过任何处理,这样aop代理才能捕获到方法的异常,并且回滚。默认情况下aop只捕获RuntimeException的异常。

@Transactional(rollbackOn = { Exception.class })  
public void method() throws Exception { 
    try{ 
       service.save1(User1);  
       service.save2(User2);
     }catch(Exception e){
        throw new RuntimeException(); 
     }          
} 
4)手动回滚。既可以捕获异常,事务又可以回滚。

加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); save2方法出现异常,save1方法是会被回滚的。

@Transactional(rollbackOn = { Exception.class })  
public void method() throws Exception { 
    try{ 
       service.save1(User1);  
       service.save2(User2);
     }catch(Exception e){
        e.printStackTrace(); 
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
     }          
} 

四、声明式事务处理情况

Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。

在@Transaction注解中定义noRollbackFor和RollbackFor指定某种异常是否回滚。
@Transaction(noRollbackFor=RuntimeException.class)
@Transaction(RollbackFor=Exception.class)
这样就改变了默认的事务处理方式。

五、事务传播机制

Propagation支持7种不同的传播机制:

1)REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

2)SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。

3)NOT_SUPPORTED:总是非事务地执行,并挂起任何存在的事务。

4)REQUIRESNEW:总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。

5)MANDATORY:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。

6)NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常

7)NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。

六、注意

1)在需要事务管理的地方加@Transactional注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。

2)@Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

3)注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。

4)通过 元素的 “proxy-target-class” 属性值来控制是基于接口的还是基于类的代理被创建。如果 “proxy-target-class” 属值被设置为 “true”,那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 “proxy-target-class” 属值被设置为 “false” 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。

5)Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

6)@Transactional 的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个方法调用另一个方法有事务的方法,事务是不会起作用的。

猜你喜欢

转载自blog.csdn.net/a1786223749/article/details/78330871