Spring 中的事务操作分为两类:
事务在 MySQL 有 3 个重要的操作:开启事务、提交事务、回滚事务,它们对应的操作命令如下:
-- 开启事务
start transaction;
-- 业务执⾏
-- 提交事务
commit;
-- 回滚事务
rollback;
Spring ⼿动操作事务和上⾯ MySQL 操作事务类似,它也是有 3 个重要操作步骤:
SpringBoot 内置了两个对象,DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或回滚事务的,⽽ TransactionDefinition 是事务的属性,在获取事务的时候需要将TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus,实现代码如下:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired // JDBC 事务管理器
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired // 定义事务属性
private TransactionDefinition transactionDefinition;
// 此方法中使用编程式事务 (手动挡)
@RequestMapping("/add")
public int add(UserInfo userInfo){
// 非空校验
if (userInfo==null || !StringUtils.hasLength(userInfo.getUsername())||!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
// 获取事务(开启事务)
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
// 插入数据库
int result=userService.add(userInfo);
System.out.println("受影响的行数:"+result);
// 提交事务
dataSourceTransactionManager.commit(transactionStatus);
// 回滚事务
//dataSourceTransactionManager.rollback(transactionStatus);
return result;
}
}

此时执行代码向数据库中插入记录,设置了commit就能成功插入,设置rollback就插入失败;
从上述代码可以看出,以上代码虽然可以实现事务,但操作很繁琐,这时候就可以使用声明式事务(自动挡)来简化代码:
声明式事务的实现很简单,只需要在需要的⽅法(或类)上添加 @Transactional 注解就可以实现了,⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务,具体实现代码如下:
// 此方法中使用声明式事务 (自动挡)
// 在进入方法之前自动开启事务,在方法执行完之后,自动提交事务,如果出现异常,则自动回滚事务
@Transactional
@RequestMapping("/add2")
public int add2(UserInfo userInfo){
// 非空校验
if (userInfo==null || !StringUtils.hasLength(userInfo.getUsername())||!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
int result=userService.add(userInfo);
System.out.println("受影响的行数:"+result);
// int n=10/0;
return result;
}

测试:
给数据库中插入用户(用户名+密码):


此时数据库中只有一条记录:

此时访问对应接口,并给接口中传入username和password:

插入成功执行:


但如果代码中出现触发异常的语句:(被除数为0会触发算术异常)

此时再访问接口:(就会报500的错误,事务就会回滚,插入数据就不成功了)


可以看到插入一开始是成功了,返回受影响的行数为1,但是此时查看数据库,发现没有这条插入的记录(事务回滚了):

这时如果手动的处理(try catch)这个异常,代码就会认为这个异常是可控的,就不会触发事务的回滚操作:

访问接口:


可以看到虽然代码中出现了异常,但是事务不会回滚,数据插入成功:

这与我们的理念(代码出现异常事务就得回滚)不符合,解决这个问题的方式有如下2种方法:
@Transactional
@RequestMapping("/add3")
public int add3(UserInfo userInfo){
// 非空校验
if (userInfo==null || !StringUtils.hasLength(userInfo.getUsername())||!StringUtils.hasLength(userInfo.getPassword())){
return 0;
}
int result=userService.add(userInfo);
System.out.println("受影响的行数:"+result);
try {
int n=10/0;
}catch (Exception e){
// 解决方案1:异常
// 抛出去
// throw e;
//解决方案2:
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return result;
}

此时设置了手动回滚后,出现异常的代码即使被try catch处理也会触发事务的回滚:


虽然返回受影响的行数为1,插入成功,但是数据库中并没有这条记录:

事务回滚(rollback)成功~
@Transactional 可以⽤来修饰⽅法或类:
@Transactional 可以设置的参数如下图:

具体使用说明讲解:

@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认
情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。
@Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇
到的异常,则回滚事务。
@Transactional 实现思路预览:

@Transactional 具体执⾏细节如下图所示:

事务有4 大特性(ACID),原⼦性、持久性、⼀致性和隔离性,具体概念如下:
上⾯ 4 个属性,可以简称为ACID。
原⼦性(Atomicity,或称不可分割性)
⼀致性(Consistency)
隔离性(Isolation,⼜称独⽴性)
持久性(Durability)
⽽这 4 种特性中,只有隔离性(隔离级别)是可以设置的(设置事务的隔离级别是⽤来保障多个并发事务执⾏更可控,更符合操作者预期的;事务的隔离级别是为了防⽌其他的事务影响当前事务执⾏的⼀种策略)

关于 mysql 中的事务的隔离级别,可以参考业内大佬的这篇文章:面试突击61:说一下MySQL事务隔离级别?

补充:在数据库中通过以下 SQL 查询全局事务隔离级别和当前连接的事务隔离级别:
select @@global.tx_isolation,@@tx_isolation;

Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置,具体操作如下图所示:

Spring 中事务隔离级别包含以下 5 种:
Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。从上述介绍可以看出,相⽐于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了⼀个Isolation.DEFAULT(以数据库的全局事务隔离级别为主)
注意事项:
当 spring 中设置了事务隔离级别和连接的数据库(mysql)事务隔离级别发生冲突的时候,是以 spring 的为准。
spring 中的事务隔离级别机制的实现是依靠连接数据库支持事务隔离级别为基础的。
本文完结 over ~✨