spring事务失效的情况

spring中,可以通过@Transaction注解进行事务的使用,但是在使用过程中常常会存在某些情况,导致事务失效,为了避免事务失效引发的问题,我们可以记录以下一些事务失效的常见场景。

事务失效会分为两个大的类型,一是事务不生效,二是事务不回滚。

事务不生效情况:

一、访问权限非公开:

    @Transactional(rollbackFor = Exception.class)
    private int save(PaymentEntity entity) {
        return paymentDao.insert(entity);
    }

当我们所使用事务的方法中,如果其访问权限是非公开的(protected、private、default),那么事务会失效

其底层是因为:@Transaction注解的方法会执行一个事务判断,在AbstractFallbackTransactionAttributeSource类的computeTransactionAttribute方法中,判断目标方法是否为public,如果否,则直接返回null,即不支持事务:

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // Don't allow no-public methods as required.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
      return null;
    }
 
    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
 
    // First try is the method in the target class.
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if (txAttr != null) {
      return txAttr;
    }
 
    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
      return txAttr;
    }
 
    if (specificMethod != method) {
      // Fallback is to look at the original method.
      txAttr = findTransactionAttribute(method);
      if (txAttr != null) {
        return txAttr;
      }
      // Last fallback is the class of the original method.
      txAttr = findTransactionAttribute(method.getDeclaringClass());
      if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
      }
    }
    return null;
  }

二、方法被final或static修饰

@Transactional(rollbackFor = Exception.class)
    public static int save(PaymentEntity entity) {
        return paymentDao.insert(entity);
    }

spring事务的源码中,spring事务底层是通过使用了AOP,即通过jdk动态代理或cglib,帮我们生成了代理类,在代理类中实现事务功能。

如果,事务的目标方法中使用了final修饰,则会导致其代理类无法重写方法,无法添加事务功能。

 @Transactional(rollbackFor = Exception.class)
    public final int save(PaymentEntity entity) {
        return paymentDao.insert(entity);
    }

同样道理,被static修饰的静态方法,也是无法通过动态代理,添加事务功能。

三、事务方法被内部调用

    @Override
    public PaymentEntity getById(String id) {
        doCheck();
        return paymentDao.getById(id);
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void doCheck(){
        System.out.println("假设该方法用于进检查参数有效性");
    }
}

在同一个类中,内部某一个方法,调用了本类中的事务方法,会导致事务失效。

因为内部调用方式,会默认使用this调用,this指代的是当前类的实例,使用实例调用时,同上道理,事务通过AOP所生成的代理对象,未被使用,导致事务功能实际没有被添加。

-- 解决方式:

1、取消掉内部的调用,即分开两个类进行正常调用;

2、当前类中注入自身,通过调用自身bean对象的方法,实现aop代理:



@Service
public class PaymentService implements IPaymentService {
    @Resource
    private PaymentDao paymentDao;
    
    @Autowired
    private PaymentService service;
    

    @Override
    public PaymentEntity getById(String id) {
        service.doCheck();
        return paymentDao.getById(id);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doCheck(){
        System.out.println("假设该方法用于进检查参数有效性");
    }
}

3、通过ApplicationContext引入bean,而后通过bean对象调用方法,使用代理类:

这个方法是在配置有bean的情况下使用,目前通过最新的springcloud形式开发的情况,springboot中无需再自行配置,则直接使用类名首字母小写(默认是spring boot的bean注册名),当然,如果对此不太了解的,还是少用这种方法:

@Service
public class PaymentService implements IPaymentService {
    @Resource
    private PaymentDao paymentDao;

    @Autowired
    ApplicationContext applicationContext;

    @Override
    public PaymentEntity getById(String id) {
        ((PaymentService)applicationContext.getBean("paymentService")).doCheck();
        return paymentDao.getById(id);
    }

    @Transactional(rollbackFor = Exception.class)
    public void doCheck(){
        System.out.println("假设该方法用于进检查参数有效性");
    }
}

4、其他方式。。。其实说白了这种失效情况,只需要你能通过代理去调用这个事务方法即可生效,还有其他的方式可以在当前类中取得代理,基本上则都可以获取代理之后去使用方法即可解决。

四、当前类未被spring管理

即当前service类忘记加@Service这些spring的注解,会导致这个类不被spring管理,使用@Transactional自然不会生效,但是这个注解是可以使用的。

public class PaymentService implements IPaymentService {
    @Resource
    private PaymentDao paymentDao;

    @Override
    @Transactional
    public PaymentEntity getById(String id) {
        return paymentDao.getById(id);
    }
}

五、多线程调用

如果目标事务方法中,调用一个其他线程的方法,会导致事务失效。

原因其实很简单,本身事务的出现是基于同一个数据库连接,即同一个线程下,通过同时提交或同时回滚,达成事务的目的(数据库事务),当出现多线程时,其事务特性本身已经被破坏。

六、表不支持事务

如果数据库引擎是myisam的话,是不支持事务的,当然这个情况比较少见。

七、未开启事务配置

事务的使用,在spring中实际上是通过配置生效的,而不是默认生效,如果没有给项目配置事务开启,即使使用了注解也不会生效的。我们需要区分几个情况去决定:

1、spring boot项目:

spring boot项目中,会通过DataSourceTransactionManagerAutoConfiguration类,默认开启事务,所以不需要额外配置;

2、传统spring项目:

需要在applicationContext.xml文件中,手动配置事务参数,启用事务:

<!-- 配置事务管理器 --> 
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager"> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 
<tx:advice id="advice" transaction-manager="transactionManager"> 
    <tx:attributes> 
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes> 
</tx:advice> 
<!-- 用切点把事务切进去 --> 
<aop:config> 
    <aop:pointcut expression="execution(* com.susan.*.*(..))" id="pointcut"/> 
    <aop:advisor advice-ref="advice" pointcut-ref="pointcut"/> 
</aop:config> 

八、事务方法的多嵌套:


import javax.annotation.Resource;
import java.util.List;

public class PaymentService implements IPaymentService {
    @Resource
    private PaymentDao paymentDao;
    
    @Override
    public int save(PaymentEntity entity) {
        //当前被事务方法调用的非事务方法,实际存在多个事务
        getById(entity.getId());
        return paymentDao.insert(entity);
    }

    @Override
    @Transactional
    public List<PaymentEntity> queryPage(PaymentEntity entity) {
        save(entity);
        return paymentDao.queryPage(entity);
    }

    @Override
    public PaymentEntity getById(String id) {
        return paymentDao.getById(id);
    }
}

在上面代码中,queryPage是事务目标方法,执行时内部存在2个其他方法,save和queryPage,而同时,save中又存在2个方法getById和insert,那么此时,由于save方法是没有使用事务注解,但是却相当于执行了2个不同事务(一个独立方法就等同一个单独的事务),当save下执行的方法出现问题,则不会反馈到queryPage中,导致事务失效。

这个情况就是事务多嵌套使用其他方法时需要注意的失效。

事务不回滚情况:

一、错误的传播特性

使用@Transactional注解时,是可以指定propagation参数的,此参数作用是指定事务的传播特性,spring目前支持7种传播特性:

1、REQUIRED 如果当前上下文中存在事务,则加入该事务,如果不存在事务,则创建一个事务,这是默认的传播属性值。

2、SUPPORTS 如果当前上下文中存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。

3、MANDATORY 当前上下文中必须存在事务,否则抛出异常。

4、REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。

5、NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。

6、NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。

7、NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。

在一般情况下我们是使用默认的传播特性,即不需要修改参数,但是如果有某一个需求需要你去手动设置该propagation参数了,而你设置的参数值为NEVER时,就会导致事务失效无法回滚。

二、在目标事务方法中手动捕获异常

@Override
    @Transactional
    public PaymentEntity getById(String id) {
        try{
            paymentDao.getById(id);
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

手动try catch了你需要执行事务的内容,而又不返回异常,此时事务不会回滚,因为没有任何异常返回时,spring会认为执行正常。

三、手动抛出异常时类型不符合事务捕获类型

@Transactional
    public PaymentEntity getById(String id) throws Exception {
        try{
            paymentDao.getById(id);
        }catch (Exception e){
            e.printStackTrace();
            throw new Exception(e);
        }
        return null;
    }

默认情况下,spring事务只回滚RuntimeException(运行时异常)和Error(错误),对于普通异常是不符合回滚标准,不会执行回滚。

如果需要对指定异常都进行回滚,我们通常需要配置回滚标准参数 rollbackfor = 回滚标准类.class,用于令符合该参数的异常都回滚,通常我们会直接回滚所有异常,例如:

//当指定为Exception.class时,Exception及其子类都会被捕获回滚 
@Transactional(rollbackFor = Exception.class)
    public PaymentEntity getById(String id) throws Exception {
        try{
            paymentDao.getById(id);
        }catch (Exception e){
            e.printStackTrace();
            throw new Exception(e);
        }
        return null;
    }

四、事务方法嵌套使用时,事务标准不同

当事务方法A内部会使用到事务方法B时,如果这2个方法的回滚标准不同(rollbackfor参数),则容易导致回滚失效。

其他

一、大事务问题

如果事务目标方法中存在多个方法,则容易出现大事务,比如常见query方法,有些时候可能会出现许多深层的各个方法调用才会产生结果,这种情况容易造成性能问题,我们也尽可能不要随意添加一些不必要的事务。一般事务常用于增删改中比较多而已。

二、编程式事务

上述内容是基于@Transactional注解的,主要说明的是它的事务问题,这种事务称之为:声明式事务。

而spring实际还提供另一种创建事务的方式,通过手动编写代码实现的事务,这种称之为:编程式事务。

 @Autowired
   private TransactionTemplate transactionTemplate;
   
   ...
   
   public void save(final User user) {
         queryData1();
         queryData2();
         transactionTemplate.execute((status) => {
            addData1();
            updateData2();
            return Boolean.TRUE;
         })
   }

spring中提供一个TransactionTemplate类,在它的execute方法中实现事务功能。

编程式事务有以下有点:
1、能避免springAOP问题导致的事务失效;

2、能更小粒度控制事务范围,更直观。

如果在开发中,开发者是精确明白事务需要点的情况下,其实可以直接使用编程式事务会更好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值