1. 抛出查看异样
    比方你的事务控制代码如下:

@Transactional
public void transactionTest() throws IOException{

User user = new User();UserService.insert(user);throw new IOException();

}
复制代码
如果@Transactional 没有特地指定,Spring 只会在遇到运行时异样RuntimeException或者error时进行回滚,而IOException等查看异样不会影响回滚。

public boolean rollbackOn(Throwable ex) {

return (ex instanceof RuntimeException || ex instanceof Error);

}
复制代码
解决方案:

晓得起因后,解决办法也很简略。配置rollbackFor属性,例如@Transactional(rollbackFor = Exception.class)。

  1. 业务办法自身捕捉了异样
    @Transactional(rollbackFor = Exception.class)
    public void transactionTest() {
    try {

     User user = new User(); UserService.insert(user); int i = 1 / 0;

    }catch (Exception e) {

     e.printStackTrace();

    }
    }
    复制代码
    这种场景下,事务失败的起因也很简略,Spring是否进行回滚是依据你是否抛出异样决定的,所以如果你本人捕捉了异样,Spring 也无能为力。

看了下面的代码,你可能认为这么简略的问题你不可能犯这么愚昧的谬误,然而我想通知你的是,我身边简直一半的人都被这一幕困扰过。

写业务代码的时候,代码可能比较复杂,嵌套的办法很多。如果你不小心,很可能会触发此问题。举一个非常简单的例子,假如你有一个审计性能。每个办法执行后,审计后果保留在数据库中,那么代码可能会这样写。

@Service
public class TransactionService {

@Transactional(rollbackFor = Exception.class)public void transactionTest() throws IOException {    User user = new User();    UserService.insert(user);    throw new IOException();}

}

@Component
public class AuditAspect {

@Autowiredprivate auditService auditService;@Around(value = "execution (* com.alvin.*.*(..))")public Object around(ProceedingJoinPoint pjp) {    try {        Audit audit = new Audit();        Signature signature = pjp.getSignature();        MethodSignature methodSignature = (MethodSignature) signature;        String[] strings = methodSignature.getParameterNames();        audit.setMethod(signature.getName());        audit.setParameters(strings);        Object proceed = pjp.proceed();        audit.success(true);        return proceed;    } catch (Throwable e) {        log.error("{}", e);        audit.success(false);    }        auditService.save(audit);    return null;}

}
复制代码
在下面的示例中,事务将失败。起因是Spring的事务切面优先级最低,所以如果异样被切面捕捉,Spring天然不能失常处理事务,因为事务管理器无奈捕捉异样。

解决方案:

看,尽管咱们晓得在处理事务时业务代码不能自己捕捉异样,然而只有代码变得复杂,咱们就很可能再次出错,所以咱们在处理事务的时候要小心,还是不要应用申明式事务, 并应用编程式事务— transactionTemplate.execute()。

  1. 同一类中的办法调用
    @Service
    public class DefaultTransactionService implement Service {

    public void saveUser() throws Exception {

     //do something doInsert();

    }

    @Transactional(rollbackFor = Exception.class)
    public void doInsert() throws IOException {

     User user = new User(); UserService.insert(user); throw new IOException();

    }
    }
    复制代码
    这也是一个容易出错的场景。事务失败的起因也很简略,因为Spring的事务管理性能是通过动静代理实现的,而Spring默认应用JDK动静代理,而JDK动静代理采纳接口实现的形式,通过反射调用指标类。简略了解,就是saveUser()办法中调用this.doInsert(),这里的this是被实在对象,所以会间接走doInsert的业务逻辑,而不会走切面逻辑,所以事务失败。

解决方案:

计划一:解决办法能够是间接在启动类中增加@Transactional注解saveUser()

计划二:@EnableAspectJAutoProxy(exposeProxy = true)在启动类中增加,会由Cglib代理实现。

  1. 办法应用 final 或 static关键字
    如果Spring应用了Cglib代理实现(比方你的代理类没有实现接口),而你的业务办法恰好应用了final或者static关键字,那么事务也会失败。更具体地说,它应该抛出异样,因为Cglib应用字节码加强技术生成被代理类的子类并重写被代理类的办法来实现代理。如果被代理的办法的办法应用final或static关键字,则子类不能重写被代理的办法。

如果Spring应用JDK动静代理实现,JDK动静代理是基于接口实现的,那么final和static润饰的办法也就无奈被代理。

总而言之,办法连代理都没有,那么必定无奈实现事务回滚了。

解决方案:

想方法去掉final或者static关键字

  1. 办法不是public
    如果办法不是public,Spring事务也会失败,因为Spring的事务管理源码AbstractFallbackTransactionAttributeSource中有判断computeTransactionAttribute()。如果指标办法不是公共的,则TransactionAttribute返回null。

// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
复制代码
解决方案:

是将以后办法拜访级别更改为public。

  1. 谬误应用流传机制
    Spring事务的流传机制是指在多个事务办法互相调用时,确定事务应该如何流传的策略。Spring提供了七种事务流传机制:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。如果不晓得这些流传策略的原理,很可能会导致交易失败。

@Service
public class TransactionService {

@Autowiredprivate UserMapper userMapper;@Autowiredprivate AddressMapper addressMapper;@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)public  void doInsert(User user,Address address) throws Exception {    //do something    userMapper.insert(user);    saveAddress(address);}@Transactional(propagation = Propagation.REQUIRES_NEW)public  void saveAddress(Address address) {    //do something    addressMapper.insert(address);}

}
复制代码
在下面的例子中,如果用户插入失败,不会导致saveAddress()回滚,因为这里应用的流传是REQUIRES_NEW,流传机制REQUIRES_NEW的原理是如果以后办法中没有事务,就会创立一个新的事务。如果一个事务曾经存在,则以后事务将被挂起,并创立一个新事务。在以后事务实现之前,不会提交父事务。如果父事务产生异样,则不影响子事务的提交。

事务的流传机制阐明如下:

REQUIRED 如果以后上下文中存在事务,那么退出该事务,如果不存在事务,创立一个事务,这是默认的流传属性值。
SUPPORTS 如果以后上下文存在事务,则反对事务退出事务,如果不存在事务,则应用非事务的形式执行。
MANDATORY 如果以后上下文中存在事务,否则抛出异样。
REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行以后新建事务实现当前,上下文事务复原再执行。
NOT_SUPPORTED 如果以后上下文中存在事务,则挂起以后事务,而后新的办法在没有事务的环境中执行。
NEVER 如果以后上下文中存在事务,则抛出异样,否则在无事务环境上执行代码。
NESTED 如果以后上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
解决方案:

将事务流传策略更改为默认值REQUIRED。REQUIRED原理是如果以后有一个事务被增加到一个事务中,如果没有,则创立一个新的事务,父事务和被调用的事务在同一个事务中。即便被调用的异样被捕捉,整个事务依然会被回滚。

  1. 没有被Spring治理
    // @Service
    public class OrderServiceImpl implements OrderService {
    @Transactional
    public void updateOrder(Order order) {

     // update order

    }
    }
    复制代码
    如果此时把 @Service 注解正文掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 治理了,事务天然就生效了。

解决方案:

须要保障每个事务注解的每个Bean被Spring治理。

  1. 多线程
    @Service
    public class UserService {

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private RoleService roleService;

    @Transactional
    public void add(UserModel userModel) throws Exception {

     userMapper.insertUser(userModel); new Thread(() -> {      try {          test();      } catch (Exception e) {         roleService.doOtherThing();      } }).start();

    }
    }

@Service
public class RoleService {

@Transactionalpublic void doOtherThing() {     try {         int i = 1/0;         System.out.println("保留role表数据");     }catch (Exception e) {        throw new RuntimeException();    }}

}