一、事务简介
事务:就是将一组操作封装成为一个整体执行单元,要么全部执行,要么都不执行。
假如事务执行了一半发生了错误就会对已经执行的部分进行回滚操作。
常见的应用场景就是转账事务,转账过程中发生错误就会全部回滚到事务最初的状态。
二、在Spring中实现事务
在Spring中实现事务有两种方式:
- 编程式事务(通过写代码操作事务);
- 声明式事务(通过注解开启和提交事务)。
编程式事务
在编程式事务中包含如下三个重要步骤:
- 获取事务;
- 提交事务;
- 回滚事务;
在编程式事务中,需要用到以下的两个对象:
- DataSourceTractionManager;
- TractionDefinition;
实现一个事务实现添加用户到数据库:
首先定义Mapper接口,在该接口中定义增加用户的方法。
@Mapper public interface UserMapper { int add(UserInfo userInfo) ;//添加用户返回受影响的行数 }
在resources文件夹下新建mapper文件夹生成UserMapper.xml文件,其中新增用户的标签如下:
<insert id="add"> insert into userinfo(username,password) values (#{username},#{password}) </insert>
然后在service包下创建UserService类:
@Service public class UserService { @Autowired UserMapper userMapper; public int add(UserInfo userInfo){ return userMapper.add(userInfo); } }
controller包下的UserController类的内容如下:
@RestController public class UserController { @Autowired private UserService userService; @Autowired private DataSourceTransactionManager transactionManager; @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 transaction = transactionManager.getTransaction(transactionDefinition); int result = userService.add(userInfo); System.out.println(result); //回滚事务 transactionManager.rollback(transaction); return result; } }
那么进行访问add:
此处的返回值为1,表示新增了1行,但是对事物进行了回滚操作,就会发现数据库中的内容不变。如果使用transactionManager.commit(transaction)就会对事务进行提交,就会在数据库中看到新插入的内容。
声明式事务
声明式事务相较于编程式事务简单了许多,只需要在用到事务的方法之前添加@Transational注解就可以实现开启事务和提交事务,如果方法执行过程中发生了异常,会自动进行回滚操作。
使用@Transactional注解修饰的方法必须是public,否则无效,并且当@Transactional修饰类时,会对类中所有的public方法有效。
@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); return result; }
访问add2:
并且在数据库中也增加了该用户:
那么中间出现异常,再次测试:
@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); int a = 99 / 0; return result; }
代码中的异常发生在添加之后,在数据中查看:
并没有添加成功,此时事务进行了回滚操作。
在使用@Transactional注解的方法中如果使用try-catch处理了异常之后,此时发生异常就不会进行回滚,就需要在catch语句中手动进行回滚操作:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
也可以直接抛出异常,不使用try-catch进行处理了。
@Transactional注解的实现原理
@Transactional注解是基于AOP思想实现的,而AOP又是基于动态代理实现的。
该注解在执行业务之前,会通过代理开启事务,在执行成功之后提交事务,或者遇到异常进行事务回滚。
三、事务的传播机制
事务的传播机制:多个事务相互调用时,事务之间是如何传递的。
事务的传播机制与多事务并发的区别:
事务传播机制的类别
REQUIRED:默认的事务传播级别,如果当前存在事务,就加入该事务,如果当前没有事务就创建一个新的事务(有房子就可以结婚,没房子买房才能结婚)
SUPPORTS:如果当前存在事务,就加入该事务,如果当前没有事务就以非事务的方式运行(有房子就可以结婚,没房子租房也可以结婚)
MANDATORY:如果当前存在事务,就加入该事务,如果当前没有事务就抛出异常(有房子就可以结婚,没房子就分手)
REQUIRES_NEW:创建一个新事务,如果当前存在事务就把当前事务挂起。(不管之前有没有房子都必须买房)
NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务就降档前事务挂起(不管之前有没有房子都必须租房子住)
NEVER:以非事务的方式运行,如果当前存在事务就抛出异常(如果之前有房子就分手)
NESTED:如果当前存在事务就创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务就创建一个新的事务(如果之前有房子,就把他作为备用再买一套房,如果之前没房,也要买房)
场景演示:
使用REQUIRED的隔离级别:
UserController中的代码:
@Transactional( propagation = Propagation.REQUIRED) @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); ArticleInfo articleInfo = new ArticleInfo(); articleInfo.setTitle("test"); articleInfo.setContent("uihjhj"); articleInfo.setUid(userInfo.getId()); articleInfo.setRcount(result); articleService.add(articleInfo); try { int m = 99/0; } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return result; }
UserService的代码:
@Transactional( propagation = Propagation.REQUIRED) public int add(UserInfo userInfo){ return userMapper.add(userInfo); }
ArticleService中的代码:
@Transactional( propagation = Propagation.NESTED) public int add(ArticleInfo articleInfo){ int result = articleMapper.add(articleInfo); try { int num = 10 / 0; } catch (Exception e) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return result; }
日志显示已经插入数据成功:
但是访问数据库:
并没有将想要新增的数据添加进去, 因为REQUIRED隔离级别要求如果当前存在事务,就加入该事务,所以发现异常,事务整个进行回滚。
使用REQUIRED_NEW隔离级别处理同样的代码,日志显示:
数据库显示:
REQUIRED_NEW隔离级别表示如果之前存在事务就将其挂起,然后创建新的事务,所以这两个事务互不干扰,因此插入用户成功。
使用NOT_SUPPORTED注解表示以非事务方式运行也会插入成功,日志显示:
数据库查询:
使用NESTED隔离级别也会插入成功,NESTED隔离级别表示之前的事务作为备用,嵌套事务之所以能够实现部分事务的回滚,是因为事务中有一个保存点,嵌套事务进入之后就相当于新建了一个保存点,回滚时只会回滚到当前的保存点,之前的事务不受影响。