博主
258
258
258
258
专辑

第十四节 SpringBoot使用事务控制

亮子 2021-05-11 13:51:50 6784 0 0 0

在Spring中,数据库事务是通过AOP技术来提供服务的。对于声明式事务,是使用@Transactional进行标注的。在@Transactional允许配置许多事物的属性,如事务的隔离级别与传播行为。

1、隔离级别(isolation)

数据库标准提出了4种isolation事务隔离级别,分别为:未提交读、读写提交、可重复读和串行化。源码如下:

package org.springframework.transaction.annotation;

/***
 * 隔离级别数字数字配置含义:
 * -1:数据库默认隔离级别
 *  1:未提交读
 *  2:读写提交
 *  4:可重复读
 *  8:串行化
 */
public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int value;

    private Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

SpringBoot设置默认隔离级别配置:

spring.datasource.tomcat.default-transaction-isolation=2

1)、读未提交

未提交读(READ_UNCOMMITTED)是最低的隔离级别,其含义是允许一个事物读取另一个事物没提交的数据。优点在于并发能力高,适合那些对数据一致性没有要求而追求高并发的场景,最大缺点是出现脏读。

@Transactional(isolation = Isolation.READ_UNCOMMITTED)

例子讲解:

图片alt

T3时刻,因为采用未提交读,所以事务2可以读取事务1未提交的库存数据为1,这里当它扣减库存后则数据为0,然后它提交了事务,库存就变为了0 。,而事务1在T5时刻回滚事务,因为第一类丢失更新已经被克服,所以它不会将库存回滚到2,那么最后的结果就变为了0,这样就出现了错误。

2)、读已提交

读写提交(READ_COMMITTED),一个事务只能读取另外一个事务已提交的数据,不能读取未提交的数据。该级别克服了脏读,但不可重复读。

@Transactional(isolation = Isolation.READ_COMMITTED)

案例讲解:

(1)克服脏读

图片alt

(2)不可重复读

图片alt

3)、可重复读

可重复读(REPEATABLE_READ),目标是克服读写提交中出现的不可重复读的现象,但会出现幻读。

@Transactional(isolation = Isolation.REPEATABLE_READ)

案例讲解:

(1)克服不可重复读

图片alt

(2)出现幻读

图片alt

4)、串行化

串行化(SERIALIZABLE),是数据库最高的隔离级别,它能够完全保证数据的一致性,但性能降低了。

5)、使用合理的隔离级别

隔离级别和可能发生的现象如下:

图片alt

对于不同的数据库,支持的隔离级别也不一样:Oracle只能支持读写提交和串行化,而MySQL能够支持4种,对于Oracle默认的隔离级别为读写提交,MySQL则是可重复读。

2、传播行为(pragation)

Spring事务机制中对数据库存在7种传播行为,源码如下:

package org.springframework.transaction.annotation;

public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

1)、 REQUIRED(0)

需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法。

2)、SUPPORTS(1)

支持事务,如果当前存在事务,就沿用当前事务,如果不存在,则继续采用无事务的方式运行子方法。

3)、 MANDATORY(2)

必须使用事务,如果当前没有事务,则会抛出异常,如果存在当前事务,就沿用当前事务。

4)、 REQUIRES_NEW(3)

无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立。

5)、 NOT_SUPPORTED(4)

不支持事务,当前存在事务时,将挂起事务,运行方法。

6)、 NEVER(5)

不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行。

7)、 NESTED(6)

在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的SQL,而不回滚当前方法的事务。

常用的传播行为主要有三种:REQUIRED 、REQUIRES_NEW、 NESTED。

3、@Transactional的自调用失效问题

注解@transactional的底层实现是Spring AOP技术,而Spring AOP技术使用的是动态代理。这就意味着对于静态(static)方法和非public方法,注解@Transactional是失效的。
自调用是指一个类的一个方法去调用自身另外一个方法的过程。在自调用的过程中,是类自身的调用,而不是代理对象去调用, 那么就不会产生 AOP,这样 Spring就不能把你的代码植入到约定的流程中。
为了克服这个问题,一方面可以写两个Service,用一个Service去调用另一个Service,这样就是代理对象的调用。Spring才会将你的代码植入事务流程。另一方面,也可以从Spring IoC容器中获取代理对象来启用AOP。从Spring IoC容器中获取代理对象的代码实例如下:

import com.springboot.web.dao.UserDao;
import com.springboot.web.model.User;
import com.springboot.web.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class UserServiceImpl implements UserService, ApplicationContextAware {

    @Autowired
    UserDao userDao;

    private ApplicationContext applicationContext;

    //实现生命周期方法,设置Ioc容器
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
    public void createUsers(List<User> userList){
        UserService userService = applicationContext.getBean(UserService.class);
        for(User user : userList){
            userService.createUser(user);
        }
    }
    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
    public void createUser(User user){
        userDao.createUser(user);
    }
}