Spring事务

目录

一、Spring事务基础概念

1、Spring和事务的关系

2、Spring事务三要素

3、事务的基本原理

4、Spring事务配置

二、事务类型

三、事务的隔离级别

四、事务的传播行为(传播属性)

五、事务失效

六、事务失效源码分析

1.失效场景

2.原理分析


一、Spring事务基础概念

1、Spring和事务的关系

  • 事务性资源:如关系型数据库Mysql、Oracle、某些消息队列中间件等,因为它们本身支持事务,也能够处理事务。
  • Spring不是事务性资源,但是它可以管理事务性资源,所以Spring和事务之间是管理关系。

2、Spring事务三要素

  • 数据源:表示具体的事务性资源,是事务的真正处理者,如MySQL等。
  • 事务管理器:像一个大管家,从整体上管理事务的处理过程,如打开、提交、回滚等。
  • 事务应用和属性配置:像一个标识符,表明哪些方法要参与事务,如何参与事务,以及一些相关属性如隔离级别、超时时间等。

3、事务的基本原理

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:

  • 获取连接 Connection con = DriverManager.getConnection()
  • 开启事务con.setAutoCommit(true/false);
  • 执行CRUD
  • 提交事务/回滚事务 con.commit() / con.rollback();
  • 关闭连接 conn.close();

使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。那么Spring是如何在我们书写的 CRUD 之前和之后开启事务和关闭事务的呢?解决这个问题,也就可以从整体上理解Spring的事务管理实现原理了。

4、Spring事务配置

SpringBoot 使用事务非常简单,首先使用注解 @EnableTransactionManagement 开启事务支持后,然后在访问数据库的Service方法上添加注解 @Transactional 便可。

以注解方式为例子:
1、配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
2、spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
3、真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

二、事务类型

分为两种:编程式事务、声明式事务

(1)编程式事务

编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。很少用,但是可以看下官网的例子:

public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;
    
    // 使用构造对transactionTemplate进行初始化
    // 需要提供一个transactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new  TransactionTemplate(transactionManager);
    }

    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus status) {
                // 这里实现自己的相关业务逻辑
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

在上面的例子中,显示的使用了 TransactionTemplate 来完成事务管理,通过实现 TransactionCallback 接口并在其 doInTransaction 方法中完成了我们对业务的处理。我们可以大概看下 TransactionTemplateexecute 方法的实现,实际上就是通过一个 TransactionCallback 封装了业务逻辑,然后 TransactionTemplate 会在事务的上下文中调用:

public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");

        if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
            return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
        }
        else {
            // 1.通过事务管理器开启事务
            TransactionStatus status = this.transactionManager.getTransaction(this);
            T result;
            try {
                // 2.执行传入的业务逻辑
                result = action.doInTransaction(status);
            }
            catch (RuntimeException | Error ex) {
                // 3.出现异常,进行回滚
                rollbackOnException(status, ex);
                throw ex;
            }
            catch (Throwable ex) {
                // 3.出现异常,进行回滚
                rollbackOnException(status, ex);
                throw new UndeclaredThrowableException(ex, 
                     "TransactionCallback threw undeclared checked exception");
            }
            // 4.正常执行完成的话,提交事务
            this.transactionManager.commit(status);
            return result;
        }
    }

(2)声明式事务

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理也有两种常用的方式,一种是基于xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

三、事务的隔离级别

1.首先说明一下事务并发引起的三种情况:

1) Dirty Reads 脏读 

一个事务正在对数据进行更新操作,但是更新还未提交,另一个事务这时也来操作这组数据,并且读取了前一个事务还未提交的数据,而前一个事务如果操作失败进行了回滚,后一个事务读取的就是错误数据,这样就造成了脏读。

2) Non-Repeatable Reads 不可重复读 

一个事务多次读取同一数据,在该事务还未结束时,另一个事务也对该数据进行了操作,而且在第一个事务两次次读取之间,第二个事务对数据进行了更新,那么第一个事务前后两次读取到的数据是不同的,这样就造成了不可重复读。

3) Phantom Reads 幻像读 

第一个数据正在查询符合某一条件的数据,这时,另一个事务又插入了一条符合条件的数据,第一个事务在第二次查询符合同一条件的数据时,发现多了一条前一次查询时没有的数据,仿佛幻觉一样,这就是幻像读。

非重复度和幻像读的区别:

非重复读是指同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果集,此时发生非重复读。

幻像读是指同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发生幻像读。

表面上看,区别就在于非重复读能看见其他事务提交的修改和删除,而幻像能看见其他事务提交的插入。 

2.隔离级别:

1) DEFAULT (默认) 

这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。

2) READ_UNCOMMITTED (读未提交) 

这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。

3) READ_COMMITTED (读已提交) 

保证一个事务修改的数据提交后才能被另外一个事务读取,另外一个事务不能读取该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。 

4) REPEATABLE_READ (可重复读) 

这种事务隔离级别可以防止脏读、不可重复读,但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了不可重复读。

5) SERIALIZABLE(串行化) 

这是花费最高代价但是最可靠的事务隔离级别,事务被处理为顺序执行。除了防止脏读、不可重复读外,还避免了幻像读。

四、事务的传播行为(传播属性)

1) REQUIRED(默认属性)

如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。 
被设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务。 

2) MANDATORY 

支持当前事务,如果当前没有事务,就抛出异常。 

3) NEVER 

以非事务方式执行,如果当前存在事务,则抛出异常。 

4) NOT_SUPPORTED 

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 

5) REQUIRES_NEW 

新建事务,如果当前存在事务,把当前事务挂起。 

6) SUPPORTS 

支持当前事务,如果当前没有事务,就以非事务方式执行。 

7) NESTED 

支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。 
嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

 PROPAGATION_NESTED 与PROPAGATION_REQUIRES_NEW的区别:

它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。

使用PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA 事务管理器的支持。 
使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。

五、事务失效

1.@Transactional失效场景

(1)底层数据库引擎不支持事务

如果数据库引擎不支持事务,则Spring自然无法支持事务。

(2)在非public修饰的方法使用

@Transactional注解使用的是AOP,在使用动态代理的时候只能针对public方法进行代理,源码依据在AbstractFallbackTransactionAttributeSource类中的computeTransactionAttribute方法中,如下:

protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
}

此处如果不是标注在public修饰的方法上并不会抛出异常,但是会导致事务失效。

(3)异常被 " 踹死了 "

这种情况小白是最容易犯错的,在整个事务的方法中使用try-catch,导致异常无法抛出,自然会导致事务失效。伪代码如下:

@Transactional
public void method(){
  try{
    //插入一条数据
    //更改一条数据
  }catch(Exception ex){
    return;
  }
}

(4)方法中调用同类的方法

简单的说就是一个类中的A方法(未标注声明式事务)在内部调用了B方法(标注了声明式事务),这样会导致B方法中的事务失效。

代码如下:

public class Test{
  public void A(){
    //插入一条数据
    //调用B方法
    B();
  }
  
  @Transactional
  public void B(){
    //插入数据
  }
}

为什么会失效呢?:其实原因很简单,Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效。

(5)rollbackFor属性设置错误

很容易理解,指定异常触发回滚,一旦设置错误,导致一些异常不能触发回滚,此时的声明式事务不就失效了吗。

默认情况下@Transactional注解会对unchecked异常进行回滚,对checked不进行回滚。

那什么事unchecked,什么事checked?通俗的说,编译器能检测到的是checked,检测不到的是unchecked

派出于Error或者RuntimeException(比如空指针,1/0)的异常为unchecked异常

继承自Exception的异常统称为checked异常,如IOException、TimeoutException

(6)noRollbackFor属性设置错误

抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

(7)propagation属性设置错误

  • 事务的传播属性在上面已经介绍了,默认的事务传播属性是Propagation.REQUIRED,但是一旦配置了错误的传播属性,也是会导致事务失效,如下三种配置将会导致事务失效:

    • Propagation.SUPPORTS

    • Propagation.NOT_SUPPORTED

    • Propagation.NEVER

(8)原始SSM项目,重复扫描导致事务失效

  • 在原始的SSM项目中都配置了context:component-scan并且同时扫描了service层,此时事务将会失效。

  • 按照Spring配置文件的加载顺序来说,会先加载Springmvc的配置文件,如果在加载Springmvc配置文件的时候把service也加载了,但是此时事务还没加载,将会导致事务无法成功生效。

  • 解决方法很简单,把扫描service层的配置设置在Spring配置文件或者其他配置文件中即可。

六、事务失效源码分析

1.失效场景

第一种

Transactional注解标注方法修饰符为非public时,@Transactional注解将会不起作用。例如以下代码。

定义一个错误的@Transactional标注实现,修饰一个默认访问符的方法

@Component
public class TestServiceImpl {
    @Resource
    TestMapper testMapper;
    
    @Transactional
    void insertTestWrongModifier() {
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }

}

在同一个包内,新建调用对象,进行访问。

@Component
public class InvokcationService {
    @Resource
    private TestServiceImpl testService;
    public void invokeInsertTestWrongModifier(){
        //调用@Transactional标注的默认访问符方法
        testService.insertTestWrongModifier();
    }
}

测试用例

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
   @Resource
   InvokcationService invokcationService;

   @Test
   public void  testInvoke(){
      invokcationService.invokeInsertTestWrongModifier();
   }
}

以上的访问方式,导致事务没开启,因此在方法抛出异常时,testMapper.insert(new Test(10,20,30));操作不会进行回滚。如果TestServiceImpl#insertTestWrongModifier方法改为public的话将会正常开启事务,testMapper.insert(new Test(10,20,30));将会进行回滚。

第二种

在类内部调用调用类内部@Transactional标注的方法。这种情况下也会导致事务不开启。示例代码如下。

设置一个内部调用

@Component
public class TestServiceImpl implements TestService {
    @Resource
    TestMapper testMapper;

    @Transactional
    public void insertTestInnerInvoke() {
        //正常public修饰符的事务方法
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }


    public void testInnerInvoke(){
        //类内部调用@Transactional标注的方法。
        insertTestInnerInvoke();
    }

}

测试用例。

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

   @Resource
   TestServiceImpl testService;

   /**
    * 测试内部调用@Transactional标注方法
    */
   @Test
   public void  testInnerInvoke(){
       //测试外部调用事务方法是否正常
      //testService.insertTestInnerInvoke();
       //测试内部调用事务方法是否正常
      testService.testInnerInvoke();
   }
}

上面就是使用的测试代码,运行测试知道,外部调用事务方法能够征程开启事务,testMapper.insert(new Test(10,20,30))操作将会被回滚;

然后运行另外一个测试用例,调用一个方法在类内部调用内部被@Transactional标注的事务方法,运行结果是事务不会正常开启,testMapper.insert(new Test(10,20,30))操作将会保存到数据库不会进行回滚。

第三种

事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。示例代码如下。

@Component
public class TestServiceImpl implements TestService {
    @Resource
    TestMapper testMapper;

    @Transactional
    public void insertTestCatchException() {
        try {
            int re = testMapper.insert(new Test(10,20,30));
            if (re > 0) {
                //运行期间抛异常
                throw new NeedToInterceptException("need intercept");
            }
            testMapper.insert(new Test(210,20,30));
        }catch (Exception e){
            System.out.println("i catch exception");
        }
    }
    
}

测试用例代码如下。

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {

   @Resource
   TestServiceImpl testService;

   @Test
   public void testCatchException(){
      testService.insertTestCatchException();
   }
}

运行测试用例发现,虽然抛出异常,但是异常被捕捉了,没有抛出到方法 外, testMapper.insert(new Test(210,20,30))操作并没有回滚。

以上三种就是@Transactional注解不起作用,@Transactional注解失效的主要原因。下面结合spring中对于@Transactional的注解实现源码分析为何导致@Transactional注解不起作用。

2.原理分析:

第一种

@Transactional注解标注方法修饰符为非public时,@Transactional注解将会不起作用。这里分析 的原因是,@Transactional是基于动态代理实现的,@Transactional注解实现原理中分析了实现方法,在bean初始化过程中,对含有@Transactional标注的bean实例创建代理对象,这里就存在一个spring扫描@Transactional注解信息的过程,不幸的是源码中体现,标注@Transactional的方法如果修饰符不是public,那么就默认方法的@Transactional信息为空,那么将不会对bean进行代理对象创建或者不会对方法进行代理调用

@Transactional注解实现原理中,介绍了如何判定一个bean是否创建代理对象,大概逻辑是。根据spring创建好一个aop切点BeanFactoryTransactionAttributeSourceAdvisor实例,遍历当前bean的class的方法对象,判断方法上面的注解信息是否包含@Transactional,如果bean任何一个方法包含@Transactional注解信息,那么就是适配这个BeanFactoryTransactionAttributeSourceAdvisor切点。则需要创建代理对象,然后代理逻辑为我们管理事务开闭逻辑。

spring源码中,在拦截bean的创建过程,寻找bean适配的切点时,运用到下面的方法,目的就是寻找方法上面的@Transactional信息,如果有,就表示切点BeanFactoryTransactionAttributeSourceAdvisor能够应用(canApply)到bean中,

AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class<?>, boolean)

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
   Assert.notNull(pc, "Pointcut must not be null");
   if (!pc.getClassFilter().matches(targetClass)) {
      return false;
   }

   MethodMatcher methodMatcher = pc.getMethodMatcher();
   if (methodMatcher == MethodMatcher.TRUE) {
      // No need to iterate the methods if we're matching any method anyway...
      return true;
   }

   IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
   if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
      introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
   }

    //遍历class的方法对象
   Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
   classes.add(targetClass);
   for (Class<?> clazz : classes) {
      Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
      for (Method method : methods) {
         if ((introductionAwareMethodMatcher != null &&
               introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
             //适配查询方法上的@Transactional注解信息  
             methodMatcher.matches(method, targetClass)) {
            return true;
         }
      }
   }

   return false;
}

我们可以在上面的方法打断点,一步一步调试跟踪代码,最终上面的代码还会调用如下方法来判断。在下面的方法上断点,回头看看方法调用堆栈也是不错的方式跟踪。

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

  • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {
   // Don't allow no-public methods as required.
   //非public 方法,返回@Transactional信息一律是null
   if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
   }
   //后面省略.......
 }

不创建代理对象

所以,如果所有方法上的修饰符都是非public的时候,那么将不会创建代理对象。以一开始的测试代码为例,如果正常的修饰符的testService是下面图片中的,经过cglib创建的代理对象。

如果class中的方法都是非public的那么将不是代理对象。

不进行代理调用

考虑一种情况,如下面代码所示。两个方法都被@Transactional注解标注,但是一个有public修饰符一个没有,那么这种情况我们可以预见的话,一定会创建代理对象,因为至少有一个public修饰符的@Transactional注解标注方法。

创建了代理对象,insertTestWrongModifier就会开启事务吗?答案是不会。

@Component
public class TestServiceImpl implements TestService {
    @Resource
    TestMapper testMapper;

    @Override
    @Transactional
    public void insertTest() {
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }
    
    @Transactional
    void insertTestWrongModifier() {
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }
}

原因是在动态代理对象进行代理逻辑调用时,在cglib创建的代理对象的拦截函数中CglibAopProxy.DynamicAdvisedInterceptor#intercept,有一个逻辑如下,目的是获取当前被代理对象的当前需要执行的method适配的aop逻辑。

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

而针对@Transactional注解查找aop逻辑过程,相似地,也是执行一次

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

  • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

也就是说还需要找一个方法上的@Transactional注解信息,没有的话就不执行代理@Transactional对应的代理逻辑,直接执行方法。没有了@Transactional注解代理逻辑,就无法开启事务

第二种

在类内部调用调用类内部@Transactional标注的方法。这种情况下也会导致事务不开启。

经过对第一种的详细分析,对这种情况为何不开启事务管理,原因应该也能猜到;

既然事务管理是基于动态代理对象的代理逻辑实现的,那么如果在类内部调用类内部的事务方法,这个调用事务方法的过程并不是通过代理对象来调用的,而是直接通过this对象来调用方法,绕过的代理对象,肯定就是没有代理逻辑了。

其实我们可以这样玩,内部调用也能实现开启事务,代码如下。

@Component
public class TestServiceImpl implements TestService {
    @Resource
    TestMapper testMapper;

    @Resource
    TestServiceImpl testServiceImpl;


    @Transactional
    public void insertTestInnerInvoke() {
        int re = testMapper.insert(new Test(10,20,30));
        if (re > 0) {
            throw new NeedToInterceptException("need intercept");
        }
        testMapper.insert(new Test(210,20,30));
    }


    public void testInnerInvoke(){
        //内部调用事务方法
        testServiceImpl.insertTestInnerInvoke();
    }

}

上面就是使用了代理对象进行事务调用,所以能够开启事务管理,但是实际操作中,没人会闲的蛋疼这样子玩

第三种

事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。

这种的话,可能我们比较常见,问题就出在代理逻辑中,我们先看看源码里卖弄动态代理逻辑是如何为我们管理事务的。

TransactionAspectSupport#invokeWithinTransaction

代码如下。

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
      throws Throwable {

   // If the transaction attribute is null, the method is non-transactional.
   final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
   final PlatformTransactionManager tm = determineTransactionManager(txAttr);
   final String joinpointIdentification = methodIdentification(method, targetClass);

   if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
      // Standard transaction demarcation with getTransaction and commit/rollback calls.
       //开启事务
      TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
      Object retVal = null;
      try {
         // This is an around advice: Invoke the next interceptor in the chain.
         // This will normally result in a target object being invoked.
          //反射调用业务方法
         retVal = invocation.proceedWithInvocation();
      }
      catch (Throwable ex) {
         // target invocation exception
          //异常时,在catch逻辑中回滚事务
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      finally {
         cleanupTransactionInfo(txInfo);
      }
       //提交事务
      commitTransactionAfterReturning(txInfo);
      return retVal;
   }

   else {
     //....................
   }
}

所以看了上面的代码就一目了然了,事务想要回滚,必须能够在这里捕捉到异常才行,如果异常中途被捕捉掉,那么事务将不会回滚。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值