spring boot 之 事务

内容是小老弟的一些整理和个人思考总结,知识的海洋那么大,有错误的话还请诸位大佬指点一下!

事务是一个不可分割操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。

事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。

在应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。

事务的四个特性

原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。

一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。

隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。

持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

事务隔离级别

序号 隔离级别 描述
1 PROPAGATION_REQUIRED (默认值)如果当前存在事务,则在该事务中执行;否则,创建一个新的事务并在其中执行方法。
2 PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
3 PROPAGATION_MANDATORY 强制执行当前事务,如果当前没有事务,则抛出异常。
4 PROPAGATION_REQUIRES_NEW 创建新的事务,并在新事务中执行方法,如果当前存在事务,则暂停当前事务。
5 PROPAGATION_NOT_SUPPORTED 以非事务方式执行方法,如果当前存在事务,则暂停当前事务。
6 PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
7 PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务中执行;否则,执行与PROPAGATION_REQUIRED相同的操作。嵌套事务是当前事务的子事务,并与当前事务共享一部分数据源连接。 如果嵌套事务失败,则仅回滚嵌套事务,而不会回滚当前事务。如果当前事务失败,则嵌套事务和当前事务都将被回滚。

事务传播行为

序号 传播行为 描述
1 ISOLATION_DEFAULT 默认隔离级别,由底层的数据库驱动决定隔离级别,通常为数据库的默认隔离级别。
2 ISOLATION_READ_UNCOMMITTED 该隔离级别表示一个事务可以读取另一个事务修改但未提交的数据。该级别可以产生脏读、不可重复读和幻读的问题。
3 ISOLATION_READ_COMMITTED 该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以避免脏读问题,但不可重复读和幻读问题仍然可能发生。
4 ISOLATION_REPEATABLE_READ 该隔离级别表示一个事务在执行过程中多次读取同一数据集时,其结果是一致的。该级别可以避免脏读和不可重复读问题,但仍然可能发生幻读问题。
5 ISOLATION_SERIALIZABLE 该隔离级别表示一个事务在执行过程中对于数据的修改会进行排队,即串行化执行,从而避免了脏读、不可重复读和幻读问题,但也降低了并发性能。

编程式事务

方式一:PlatformTransactionManager

java 复制代码
@Autowired
private PlatformTransactionManager platformTransactionManager;

@Test
public void test(){
    DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
    defaultTransactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
    defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus transaction = platformTransactionManager.getTransaction(defaultTransactionDefinition);
    try {
        // 业务代码
        // doSomeThing();
        // 提交事务
        platformTransactionManager.commit(transaction);
    } catch (Exception e) {
        platformTransactionManager.rollback(transaction);
        throw new ServiceException("特务异常,事务回滚");
    }
}

方式二:TransactionTemplate

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;
// 配置
@Configuration
public class TransactionConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean("transactionTemplate")
    public TransactionTemplate transactionTemplate() {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        // 配置事务隔离级别
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        // 配置事务传播行为
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        // 以下配置按需配置即可
        // def.setTimeout(5);  // 设置超时时间为5秒
        // def.setReadOnly(false);
        return new TransactionTemplate(transactionManager(), def);
    }
}
java 复制代码
@Autowired
@Qualifier("transactionTemplate")
private TransactionTemplate transactionTemplate;

@Test
public void test02() {
    // new TransactionCallback<Integer> 可更改返回值类型,按需更改即可
    // execute 业务代码执行后的返回值
    Integer execute = transactionTemplate.execute(new TransactionCallback<Integer>() {
        @Override
        public Integer doInTransaction(TransactionStatus transactionStatus) {
            int i = 0;
            try {
                Student student = new Student();
                student.setRemark("事务添加测试");
                i = studentMapper.addOne(student);
            } catch (Exception e) {
                transactionStatus.setRollbackOnly();
                throw new RuntimeException(e);
            }
            return i;
        }
    });
}

声明式事务

@Transactional常用配置

参数名称 功能描述
readOnly 用于设置当前事务是否为只读事务,设置为 true 表示只读,false 则表示可读写,默认值为 false。 例如:@Transactional(readOnly=true)
rollbackFor 用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。 例如: 指定单一异常类:@Transactional(rollbackFor=RuntimeException.class); 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
transactionManager / value 多个事务管理器托管在 Spring 容器中时,指定事务管理器的 bean 名称
rollbackForClassName 用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。 例如: 指定单一异常类名称 @Transactional(rollbackForClassName="RuntimeException") 指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"})
noRollbackFor 用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。 例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class) 指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
noRollbackForClassName 用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。 例如: 指定单一异常类名称:@Transactional(noRollbackForClassName="RuntimeException") 指定多个异常类名称:@Transactional(noRollbackForClassName={"RuntimeException", "Exception"})
propagation 用于设置事务的传播行为。 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED, readOnly=true)
isolation 用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置
timeout 该属性用于设置事务的超时秒数,默认值为 - 1 表示永不超时 事物超时设置:@Transactional(timeout=30) ,设置为 30 秒

@Transactional失效场景

1、权限修饰符问题

java 复制代码
@Override
@Transactional
// 被@Transactional注解的方法访问权限必须是public,否则失效,
// 且方法不可用final或者static关键字修饰
public void addOne() {
    Student student = new Student();
    student.setRemark("事务添加测试");
    studentMapper.addOne(student);
}

2、注解属性 rollbackFor设置错误

rollbackFor可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException的异常)或者 Error才回滚事务,其他异常不会触发回滚事务。

java 复制代码
// 一般设置为Exception即可,程序中若catch捕获了异常,一定要抛出,否则也会失效,
// 即在 catch 中最后加上 throw new RuntimeExcetpion() 抛出运行异常
@Transactional(rollbackFor = Exception.class)
java 复制代码
@Service
@Slf4j
public class MyTransactional {
    
    // 最外层公共方法。自动回滚事务方式,insertOrder()方法报错后事务回滚,且线程中止,后续逻辑无法执行
    @Transactional
    public void test1() {
        this.insertOrder();
        // 若上面代码报错后,下面的代码便不会执行
        doSomeThingElse();
    }
    
    // 最外层公共方法。手动回滚事务方式,insertOrder()方法报错后事务回滚,可以继续执行后续逻辑
    @Transactional
    public void test2() {
        try {  
            insertOrder();
        } catch (Exception e) {  
            log.error("业务异常,事务回滚", e.getMessage());
            // 要么手动抛出异常
            // throw new RuntimeExcetpion();
            // 要么手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
        // 其他操作
        doSomeThingElse();
    }
 
    // 进行数据库操作的方法(private 或 public 均可)
    private void insertOrder() {
        // 数据库增删改查操作
    }
}

3、数据库不支持事务

Spring事务生效的前提是连接的数据库支持事务,如果底层的数据库都不支持事务,则Spring事务肯定会失效的。

例如:使用MySQL数据库,默认是InnoDB,若选用MyISAM存储引擎,因为MyISAM存储引擎本身不支持事务,因此事务毫无疑问会失效。

4、未被spring管理

若类没有被spring管理,肯定会失效。

5、多线程调用

两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务,spring的事务是通过数据库连接来实现的。当前线程中保存了一个mapkey是数据源,value是数据库连接。同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务,因为事务失效。

6、未开启事务

Spring项目:需要在applicationContext.xml文件中,手动配置事务相关参数

Spring Boot项目:通过DataSourceTransactionManagerAutoConfiguration类,默认开启了事务

7、方法内部调用

现有一个被外部调用的方法A,A中调用了操作数据库的方法B,说明如下:

方法A未声明事务@Transactional,方法B若是其他类的方法且各自声明事务,则事务由方法B自己控制;

java 复制代码
// a和b不在同一个类中场景

// a未声明事务,b声明事务,此时a中事务不会回滚,b中的事务可以回滚
// 若想使a中事务也可以回滚,在a上添加事务注解,声明事务即可
	@Override
	// @Transactional(rollbackFor = Exception.class)
    public void aaa() {
        Student student = new Student();
        student.setRemark("测试方法 A  " + DateUtil.formatDateTime(new Date()));
        studentMapper.insert(student);
        studentServiceV2.bbb();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bbb() {
        Student student = new Student();
        student.setRemark("测试方法 B  " + DateUtil.formatDate(new Date()));
        studentMapper.insert(student);
        int a = 10 / 0; // 模拟异常
    }

方法A未声明事务@Transactional,方法B若是本类的方法,则无论方法B是否声明事务,事务均不会生效;

java 复制代码
// aaa和a属于同一个类
    @Override
    public void aaa() {
        Student student = new Student();
        student.setRemark("测试方法 A  " + DateUtil.formatDateTime(new Date()));
        studentMapper.insert(student);
        // a方法可以事务回滚的办法
        // 注意:此时a会回滚事务,但aaa不会回滚事务!!!!!
        ((StudentService)AopContext.currentProxy()).a();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void a() {
        Student student = new Student();
        student.setRemark("测试方法 a  " + DateUtil.formatDateTime(new Date()));
        studentMapper.insert(student);
        int a = 10 / 0; // 模拟异常
    }

方法A声明事务@Transactional,无论方法B是不是本类的方法,无论方法B是否声明事务,事务均由公共方法A控制;

java 复制代码
// aaa和a属于同一个类,b在另一个类中
// 此时只要aaa声明事务,即可实现事务回滚
	@Override
    @Transactional(rollbackFor = Exception.class)
    public void aaa() {
        Student student = new Student();
        student.setRemark("测试方法 A  " + DateUtil.formatDateTime(new Date()));
        studentMapper.insert(student);
        // studentServiceV2.bbb();
        a();
    }

    @Override
	// @Transactional(rollbackFor = Exception.class)
    public void a() {
        Student student = new Student();
        student.setRemark("测试方法 a  " + DateUtil.formatDateTime(new Date()));
        studentMapper.insert(student);
        int a = 10 / 0; // 模拟异常
    }

    @Override
    // @Transactional(rollbackFor = Exception.class)
    public void bbb() {
        Student student = new Student();
        student.setRemark("测试方法 B  " + DateUtil.formatDate(new Date()));
        studentMapper.insert(student);
        int a = 10 / 0; // 模拟异常
    }

方法A声明事务@Transactional,方法B运行异常,但运行异常被方法B自己 try-catch 处理了,则事务回滚是不会生效的;

java 复制代码
异常捕获后一定要抛出!!!

8、错误的传播特性

如果在手动设置propagation参数的时候,把传播特性设置错了,则事务会失效

目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED

9、自定义了回滚异常

@Transactional默认回滚RuntimeException运行时异常)和Error错误),但若rollbackFor 指定为自定义的异常,恰巧程序产生的异常不是自定义的异常,事务便不会回滚。

10、大事务问题

该部分其实不属于注解失效场景,算是一个开发问题。

试想:

在被@Transactional注解标记的某个方法中,有5个执行sql语句的操作,其中三个sql是查询操作,只有两个sql大概会产生事务问题,但因为注解标记的是整个方法,所以整个方法都被包含在事务中了,然后又恰巧查询的sql可能略有耗时,那整个事务便会耗时了,从而造成大事务问题。

那咋解决呢?

答案:编程式事务,能够更小粒度的控制事务的范围,更直观,且会尽量的避免声明式事务场景下事务失效问题。

当然,如果你就是非要全部添加事务,那就当我上面是在瞎扯,别杠,杠就是你对。

下述内容整理中,loading...

并发场景下事务引发的问题

脏读

幻读

不可重复读

相关推荐
小李不想输啦2 小时前
什么是微服务、微服务如何实现Eureka,网关是什么,nacos是什么
java·spring boot·微服务·eureka·架构
张铁铁是个小胖子2 小时前
微服务学习
java·学习·微服务
ggs_and_ddu2 小时前
Android--java实现手机亮度控制
android·java·智能手机
敲代码娶不了六花4 小时前
jsp | servlet | spring forEach读取不了对象List
java·spring·servlet·tomcat·list·jsp
Yhame.4 小时前
深入理解 Java 中的 ArrayList 和 List:泛型与动态数组
java·开发语言
ROCKY_8174 小时前
Mysql复习(二)
数据库·mysql·oracle
是小崔啊5 小时前
开源轮子 - EasyExcel02(深入实践)
java·开源·excel
myNameGL6 小时前
linux安装idea
java·ide·intellij-idea
青春男大6 小时前
java栈--数据结构
java·开发语言·数据结构·学习·eclipse
问道飞鱼6 小时前
【知识科普】认识正则表达式
数据库·mysql·正则表达式