Spring事务介绍

注意: 研究事务的传播行为的前提是下面两种中的任一种:

  • serviceImplA中的事务方法调用serviceImplB中的事务方法(这种情况下Mapper可以同一个);
  • 同一个XxxServiceImpl中的方法A调用B,但Mapper不能为同一个;

正如本文章靠下一点反例中说的,同一个serviceImpl的方法间调用,且Mapper还是同一个的,不在事务传播行为研究的范畴哦。

一、事务的传播行为详解

package org.springframework.transaction.annotation;

import org.springframework.transaction.TransactionDefinition;

/**
 * Enumeration that represents transaction propagation behaviors for use
 * with the {@link Transactional} annotation, corresponding to the
 * {@link TransactionDefinition} interface.
 */
public enum Propagation {
	REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),

	SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),

    MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),

	REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),

	NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),

    NEVER(TransactionDefinition.PROPAGATION_NEVER),

	NESTED(TransactionDefinition.PROPAGATION_NESTED);

}

事务的传播行为有七种。下面以【serviceA中的方法A调用serviceB中的方法B】为例说明,方法B上@Transactional()的事务传播行为属性propagation为下面各个值时:

  • REQUIRED(默认值):如果A上面有事务注解,就加入该事务;如果A上面没有事务注解,就新建一个事务。
  • REQUIRES_NEW:新建事务,如果A存在事务,就把A事务挂起(“挂起”指并列的两个事务,互不影响,而不是嵌套关系)
  • NESTED:如果A存在事务,就在这个事务里再嵌套一个完全独立的事务B,嵌套的事务B可以独立的提交和回滚;如果A没有事务,就和REQUIRED一样。
  • MANDATORY:使用A的事务,如果A没有事务,就抛出异常。
  • NEVER:以非事务方式执行,如果A存在事务,则抛出异常。
  • NOT_SUPPORTED:以非事务方式执行,如果A存在事务,就把A事务挂起。
  • SUPPORTS:支持A的事务,如果A没有事务就以非事务方式执行。

只有前两个用的多,最多再加上第三个。但为了面试都得知道。

二、测试代码

2.1 层级代码准备工作

controller层级:

@RestController
@RequestMapping("/transaction/")
public class StudentInfoTransactionController {

    @Resource(name = "service1")
    private TransactionService transactionService;

    /**
     * 插入一条学生信息insertStudent
     */
    // 访问路径为:http://localhost:8081/transaction/insertStudent
    @RequestMapping("/insertStudent")
    public int insertStudent() {
        int row = transactionService.insertStudent();
        return row;
    }
}

TransactionServiceImpl1代码:

@Service("service1")
public class TransactionServiceImpl1 implements TransactionService {

    @Resource
    private StudentInfoMapper studentInfoMapper;

    @Autowired
    @Qualifier("service2")
    private TransactionService transactionService2;

    @Override
    @Transactional
    public int insertStudent() {
        StudentInfo student = new StudentInfo();
        student.setStuName("测试事务方法1");
        student.setPhoneNumber("15536537886");
        int row = studentInfoMapper.insertStudent(student);
        //调用另外一个service中的方法
        transactionService2.insertStudent();
        //模拟异常
        int i = 1 / 0;
        return row;
    }
}

TransactionServiceImpl2代码:(它被TransactionServiceImpl1中的方法调用)

@Service("service2")
public class TransactionServiceImpl2 implements TransactionService {

    @Resource
    private StudentInfoMapper studentInfoMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
//    @Transactional(propagation = Propagation.REQUIRES_NEW)
//    @Transactional(propagation = Propagation.MANDATORY)
//    @Transactional(propagation = Propagation.NESTED)
//    @Transactional(propagation = Propagation.NEVER)
//    @Transactional(propagation = Propagation.NOT_SUPPORTED)
//    @Transactional(propagation = Propagation.SUPPORTS)
    public int insertStudent() {
        StudentInfo student = new StudentInfo();
        student.setStuName("测试事务方法2"); student.setPhoneNumber("1*****");
        int row = studentInfoMapper.insertStudent(student);
        //int i = 1/0;
        return row;
    }
}

2.2 REQUIRED和REQUIRES_NEW测试

情况一:代码如上,Bean=service1中代码调用Bean=service2中的方法,service2中的方法本身没有异常,service1中的方法在调用service2之后产生了异常。测试结果:数据库没有插入数据。即:service2的事务加入了service1的事务,两者要么同时成功,要么同时失败。日志如下:

 在情况一的基础上,如果只把service2注解的传播行为改为REQUIRES_NEW,那么数据库中便把service2的数据插入了。可见,如果是REQUIRES_NEW时,两个事务是独立的,互不影响。日志如下:

情况二:在上面情况一基础上,将service1中的注解@Transactional注释掉,再进行测试结果为:两条数据都插入成功了。service2外层没有事务,所以它会新建一个事务,service2的成功失败只在于service2中是否会出现异常;service1的成功与否当然与service2的事务无关,只与自己有关。日志如下:

 在情况一的基础上,如果只把service2注解的传播行为改为REQUIRES_NEW,测试结果为两条数据都插入了,这也是可以想到的。

2.3 NESTED测试

情况一:如果A没有事务,就和REQUIRED一样,会新建一个事务;

情况二:如果A存在事务,就在这个事务里再嵌套一个完全独立的事务B,嵌套的事务B可以独立的提交和回滚。(我测试时,发现是同一个SqlSession,并没有独立提交和回滚,实际的效果是要么都成功,要么都失败!!!)

都失败日志:

都成功日志:

2.4 其他类型的测试

MANDATORY:如果A没有事务时,就抛出异常,但并不会影响到service1中的插入结果。如果A有事务时,使用A的事务。(已测试)

NEVER:如果A存在事务,直接抛异常,service1和service2数据都不会插入。(已测试)

SUPPORTS:支持A的事务,如果A没有事务就以非事务方式执行。

NOT_SUPPORTED:service2以非事务方式执行,即service2有异常也会插入数据;而service1能否插入成功取决于service1和service2这个整体是否有异常。(已测试)

三、反例:A、B两方法在同一个类且是同一个Mapper

测试一:

controller中的代码:

@RestController
@RequestMapping("/transaction/")
public class StudentInfoTransactionController {

    @Resource(name = "service1")
    private TransactionService transactionService;

    /**
     * 插入一条学生信息insertStudent
     */
    // 访问路径为:http://localhost:8081/transaction/insertStudent
    @RequestMapping("/insertStudent")
    public int insertStudent() {
        int row = transactionService.insertStudent();
        return row;
    }
}

service层级实现类中的代码:

@Service("service1")
public class TransactionServiceImpl1 implements TransactionService {

    @Resource
    private StudentInfoMapper studentInfoMapper;

    @Override
    @Transactional
    public int insertStudent() {
        StudentInfo student = new StudentInfo();
        student.setStuName("测试事务方法1");
        student.setPhoneNumber("15536537886");
        int row = studentInfoMapper.insertStudent(student);
        //调本类中的方法insertStudent222(),这个方法没有异常产生
        insertStudent222();
        //模拟异常
        int i = 1 / 0;
        return row;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int insertStudent222() {
        StudentInfo student = new StudentInfo();
        student.setStuName("同一个类中的方法");
        student.setPhoneNumber("2....");
        int row = studentInfoMapper.insertStudent(student);
        return row;
    }
}

说明:代码如上所示,在浏览器中输入上面StudentInfoTransactionController类中的url回车,会调用TransactionServiceImpl1类中重写方法insertStudent(),在该方法内部又调用了本类中的被事务注解所标记的insertStudent222()方法,且这两个方法中的是同一个studentInfoMapper(这个前提很重要)。insertStudent222()方法本身没有异常,insertStudent()方法有异常且位置在insertStudent222()方法执行结束后。先说结论:数据库中没有任何数据插入。打印的日志如下:

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cc71227]
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@37fadc8] will be managed by Spring
==>  Preparing: insert into student_info( stu_name, phone_number, created_time, updated_time )values( ?, ?, sysdate(), sysdate() )
==> Parameters: 测试事务方法1(String), 15536537886(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cc71227]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cc71227] from current transaction
==>  Preparing: insert into student_info( stu_name, phone_number, created_time, updated_time )values( ?, ?, sysdate(), sysdate() )
==> Parameters: 同一个类中的方法(String), 2....(String)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cc71227]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cc71227]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1cc71227]

结论1:如果A方法(有@Transactional注解)调用了本类中的B方法,且A、B两个方法中的Mapper是同一个的话(如studentMapper),不管B上面是否有注解@Transactional,以及B上面@Transactional的事务传播行为是7中中的哪种,那么B方法的事务会失效,会统统纳入A方法的事务管理,打印的日志也都是上面那样的,一模一样,都是同一个事务型的SqlSession(DefaultSqlSession@1cc71227)。即:这两个方法都没有异常时,两条数据要么都插入成功;两个方法不管哪个有异常时,两条数据都不会插入成功。这种情况不属于事务传播行为研究的范畴哦。【强调:如果A、B两个方法不是同一个Mapper时结果就不一样了】

测试二:

这次测试的大前提还是A、B两方法在同一个类且是同一个Mapper,不同的是,A方法上没有事务注解哦。B上面@Transactional的事务传播行为是7中都试过了,事务会失效,就像根本没有任何事务注解@Transactional一样,即:异常之前的代码都会成功,之后的都会失败。日志都是下面这样的:

四、事务失效情形

Spring事务失效的情形有哪些?(GitCode AI答案)

Spring事务可能在以下情况下失效:

  1. 数据库不支持事务:如MySQL选择了不支持事务的MyISAM存储引擎。
  2. 事务方法未被Spring管理:如果事务方法所在的类不在Spring的IOC容器中,该方法不会受到Spring事务控制。
  3. 方法非public访问:只有public方法的事务注解才会生效。
  4. 同一类内非事务方法调用事务方法:方法A调用有事务注解的方法B,但A本身无事务注解,那么B的事务不生效。
  5. 未配置事务管理器:Spring容器中需要有事务管理器才能处理事务。
  6. 事务传播类型不正确:如果内部方法的传播类型设置为不支持事务的类型,事务将失效。
  7. 错误地捕获异常:不恰当的异常处理可能导致事务无法正常回滚。
  8. 标注错误的异常类型:@Transactional注解中指定的异常类型与实际抛出的异常不符,事务回滚可能会失效。

Spring事务失效的8大场景,坑有点多,处理不好真的是坑坑致命!_哔哩哔哩_bilibili 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值