spring的事务机制

https://blog.csdn.net/minghao0508/article/details/124374637

1、事务的基本概念:

在数据库系统中,一个事务是指:由一系列数据库操作组成的一个完整的逻辑过程;例如银行转帐,从原账户扣除金额,以及向目标账户添加金额,这两个数据库操作的总和,构成一个完整的逻辑过程,不可拆分;这个过程被称为一个事务,具有ACID特性; 

2、事务的传播行为

https://zhuanlan.zhihu.com/p/148504094

https://cuizb.top/myblog/article/detail/1667390158#%E4%BA%8B%E5%8A%A1%E7%9A%84%E4%BC%A0%E6%92%AD%E8%A1%8C%E4%B8%BA%EF%BC%88propagation%20behavior%EF%BC%89

 

指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

Spring默认事务传播行为是:propagation_required

 

1. TransactionDefinition.PROPAGATION_REQUIRED:
"如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。"

2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:
"创建一个新的事务,如果当前存在事务,则把当前事务挂起。"

3. TransactionDefinition.PROPAGATION_SUPPORTS:
"如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。"

4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
"以非事务方式运行,如果当前存在事务,则把当前事务挂起。"

5. TransactionDefinition.PROPAGATION_NEVER:
"以非事务方式运行,如果当前存在事务,则抛出异常。"

6. TransactionDefinition.PROPAGATION_MANDATORY:
"如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。"

7. TransactionDefinition.PROPAGATION_NESTED:
"如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;"
"如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。"

 

3、事务的底层实现原理

Spring AOP将通用的功能横向抽取出来作为切面,避免非业务代码侵入到业务代码中;通过@Transactional注解就能让Spring为我们管理事务,免去了重复的事务管理逻辑,减少对业务代码的侵入,让开发人员能够专注于业务层面开发;

结合上面的图,我们关注两点:

(1)判断生成代理对象:通过@Transactional注解来标记方法(定义切点),在Bean初始化过程中判断是否要对当前Bean创建代理对象,并且拿到@Transactional注解的属性;

(2)定义代理对象的回调逻辑,即执行代理逻辑:在执行目标方法前打开事务,执行过程中捕获异常执行回滚逻辑,在执行完目标方法后提交事务;

源码分析请参考我的文章《Spring事务注解@Transactional的源码分析》,将源码的流画张图总结下:

4、事务失效场景

https://blog.csdn.net/Weixiaohuai/article/details/120804192

(1)@Transactional注解未打在public方法上

以下来自 Spring 官方文档:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.

大概意思就是 @Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

(2)目标方法用final、static修饰

Spring 使用 AOP(面向切面编程)来实现事务管理。当一个对象被 Spring 托管并使用 @Transactional 注解标记时,Spring 会使用动态代理或者CGLIB(如果被代理的类没有实现接口)创建一个代理对象来处理事务。

  1. JDK 动态代理: 如果你的类有接口并且 final 方法所在的类通过 JDK 动态代理,则 final 方法可能不会被代理,因为 JDK 动态代理无法覆盖 final 方法。

  2. CGLIB 代理: 如果使用 CGLIB 代理,final 方法应该能够被代理,但可能存在其他问题或者配置错误。

  3. JDK动态代理的限制: JDK动态代理是基于接口的,它无法代理静态方法。它会生成目标类的一个实现接口的代理类,而静态方法是无法实现接口的。
    CGLIB代理的不支持: CGLIB代理可以代理类而不是接口,但它也不能代理静态方法。CGLIB是通过生成目标类的子类来实现代理,而静态方法是不能被子类覆盖

(3)同一个类中的方法直接内部调用

 方法自调用问题:非事务方法insert()中调用的自身类的事务方法insertUser()。

下面我们来看一个事务失效的例子:

@Override
public void insert(User user) {
    //如果insertUser方法内发生了异常,事务没有生效,数据还是插入进去了.
    this.insertUser(user);
}
 
@Override
@Transactional
public void insertUser(User user) {
    userMapper.insertUser(user);
    //模拟发生异常
    int i = 1 / 0;
}

启动程序,执行insert()方法,程序发生异常:

查看数据库:

我们发现数据库的数据插入成功,事务失效了,并没有回滚。

原因分析:

由于@Transactional的实现原理是AOP,而AOP的实现原理是动态代理,而自己调用自己的过程,并不存在代理对象的调用,这样就不会产生AOP去为我们设置@Transactional配置的参数,这样就出现了自调用注解失效的问题。

解决方式一:把insert(User user)和insertUser(User user)拆开来,分别放到不同的类里。

解决方式二:自己注入自己,用注入的实例调用。

如下:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
 
    @Autowired
    private UserService userService;
 
    @Override
    public void insert(User user) {
        //注意,这里不使用this调用,而是@Autowired自己(UserService)注入自己,然后调用事务方法insertUser
        userService.insertUser(user);
    }
 
    @Override
    @Transactional
    public void insertUser(User user) {
        userMapper.insertUser(user);
        //模拟发生异常
        int i = 1 / 0;
    }
 
}

启动程序,调用insert(User user)方法,程序发生异常,查看数据库:

我们发现,此时数据库数据并没有插入新的数据,事务成功回滚了,事务生效。

解决方式三:获取代理类,利用代理类调用自己类的方法。

如下:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;
 
    @Override
    public void insert(User user) {
        //获取代理类,利用代理类调用自己类的方法
        ((UserService) AopContext.currentProxy()).insertUser(user);
    }
 
    @Override
    @Transactional
    public void insertUser(User user) {
        userMapper.insertUser(user);
        //模拟发生异常
        int i = 1 / 0;
    }
 
}

再次程序,我们发现程序抛出异常,查看数据库数据:

我们发现,此时数据库数据并没有插入新的数据,事务成功回滚了,事务生效。

解决方式四:去掉insertUser(User user)上面的@Transactional注解,把它加到insert(User user)方法上, 这样能使用到 insert(User user)的事务。

@Override
@Transactional
public void insert(User user) {
    try {
        this.insertUser(user);
    } catch (Exception e) {
        e.printStackTrace();
        //注意这里,需要抛出Spring事务支持的异常类型,否则事务还是会失效的
        throw new RuntimeException();
    }
}
 
@Override
public void insertUser(User user) {
    userMapper.insertUser(user);
    //模拟发生异常
    int i = 1 / 0;
}

再次程序,我们发现程序抛出异常,查看数据库数据:

我们发现,此时数据库数据并没有插入新的数据,事务成功回滚了,事务生效。

 

 (4)事务方法所在的类未被Spring管理

如下面例子所示:

// @Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        // update order
    }

}

如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。

(5)多线程调用

@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(() -> {
            roleService.doOtherThing();
        }).start();
    }
}
 
@Service
public class RoleService {
 
    @Transactional
    public void doOtherThing() {
        System.out.println("保存role表数据");
    }
}

从上面的例子中,我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。

我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以它们是不同的事务。

 

(6)存储引擎不支持事务

如果使用mysql且存储引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务。

 

解决方式:数据表可以改成InnoDB存储引擎,支持事务。

 

(7)传播类型不支持事务,导致事务失效

@Service
public class OrderServiceImpl implements OrderService {
 
    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
 
    //以非事务方式运行
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void updateOrder(Order order) {
        // update order
    }
 
}

 

posted @ 2022-04-13 16:17  guoyu1  阅读(19)  评论(0编辑  收藏  举报