SpringBoot事务管理之@Transactional的使用

概述

什么是事务?举个简单的例子,假如小明现在打算从自己的银行账户转账5W元至小花的银行账户,这涉及到了两个操作,第一个是从小明的账户内提走5W,第二个是将这5W块汇入小花的账户,如果第一步操作正常而第二步操作出了问题,那么我们要保证第一步操作能被撤销掉,否则小明就要天台见了。。。
那么事务管理就是解决这一类问题的有效手段,它保证一组操作要么全部完成(提交Commit),要么有任何一环出现差错就要全部撤回(回滚Rollback)!

现在我们给出一个简单的事务定义:事务指的是满足 ACID 特性的一组操作。
1. 原子性(Atomicity)
事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
2. 一致性(Consistency)
数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。
3. 隔离性(Isolation)
一个事务所做的修改在最终提交以前,对其它事务是不可见的。
4. 持久性(Durability)
一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。使用重做日志来保证持久性。

SpringBoot支持的事务管理方式:

1.编程式事务管理
2.声明式事务管理

两种事务管理方式的简单对比:
编程式事务管理需要在业务逻辑代码中掺杂事务管理的代码,这样做有违Spring倡导的非侵入式的开发方式。而声明式事务管理建立在AOP之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。可以看出,声明式事务管理使业务代码不受污染。声明式事务唯一不足地方是,它的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

基于@Transactional注解的声明式事务管理简单易用,是本文要介绍的内容。

@Transactional的使用

@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性。拿方法举例,打上@Transactional 标签的方法,在执行过程中抛出unchecked异常时,会执行回滚。(什么?你问我什么是unchecked异常,就是非检查异常啦,它包括RuntimeException及其子类和Error,检查异常和非检查异常很好理解,比如空指针异常,编译时编译器是检查不出来的,只有运行程序跑起来才会抛出这个异常,这就是非检查异常,再比如IOException,这是编译期能检查出来的,这就是checked检查异常)。

接下来,我们简单的来测试一下。(本文使用MySQL数据库)

在测试之前,还要简单的补充一下,熟悉MySQL的朋友可能会知道,MySQL默认的是自动提交模式,每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果执行失败则隐式的回滚事务。对于正常的事务管理,是一组相关的操作处于一个事务之中,因此必须关闭数据库的自动提交模式。
笔者刚接触Spring事务管理时考虑过这个问题,后来查阅相关资料发现这个问题并不需要我们担心,为开发人员操碎心的Spring已经将底层连接的自动提交特性设置为false

我在JUnit单元测试中简单的写了一个测试方法,该方法向两个不同的数据表中各添加一条数据,将这两个操作看作是一个事务,两个数据表结构如下,并且开始测试之前,两个表中都没有数据。
在这里插入图片描述
在这里插入图片描述

测试一:不使用事务管理。

    //注入两个dao层爸爸
	@Autowired
	private SingleGraphDao singleGraphDao;
	@Autowired
	private GraphInfoDao graphInfoDao;
	
	@Test
	public void testA_InsertSingleGraph() {
		//创建两个实体类测试对象,并为他们简单的赋值
		GraphInfo test1 = new GraphInfo();
		test1.setGraphInfoId(1L);
		test1.setCity("夏威夷");
		
		SingleGraph test2 = new SingleGraph();
		test2.setGraphInfoId(1L);
		test2.setGraphAddr("xxx.jpg");
		
		//将两个插入操作视为一个事务
		//先执行第一个数据的插入
		graphInfoDao.insertGraphInfo(test1);
		//简单的抛出一个异常
		int i = 1;
		if (i == 1) {
			throw new RuntimeException();
		}
		//之后再执行第二个数据的插入
		singleGraphDao.insertSingleGraph(test2);
	}

此时数据表的记录状态
在这里插入图片描述
在这里插入图片描述
此时就产生了脏数据。

测试二:开启事务支持,打上@Transactional标签,并将之前的数据库记录删除掉

    //注入两个dao层大佬
	@Autowired
	private SingleGraphDao singleGraphDao;
	@Autowired
	private GraphInfoDao graphInfoDao;
	
	//相比于之前的代码,这里只是多了个@Transactional注解
	@Test
	@Transactional
	public void testA_InsertSingleGraph() {
		//创建两个实体类对象,为他们简单的赋值
		GraphInfo test1 = new GraphInfo();
		test1.setGraphInfoId(1L);
		test1.setCity("夏威夷");
		
		SingleGraph test2 = new SingleGraph();
		test2.setGraphInfoId(1L);
		test2.setGraphAddr("xxx.jpg");
		
		//将两个插入操作视为一个事务
		//先执行第一个数据的插入
		graphInfoDao.insertGraphInfo(test1);
		//简单的抛出一个异常
		int i = 1;
		if (i == 1) {
			throw new RuntimeException();
		}
		//之后再执行第二个数据的插入
		singleGraphDao.insertSingleGraph(test2);
	}

执行测试后,我们再来看看数据表的状态
在这里插入图片描述
在这里插入图片描述
可以看到,尽管代码中第一个插入操作没有出现任何问题,但是由于整个事务内抛出了RuntimeException所以第一个插入操作也被撤销了,这就是回滚操作(RollBack)

怎么样?Spring的事务管理是不是非常的简单啊~

写在最后:@Transactional注解只对public方法有效,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

猜你喜欢

转载自blog.csdn.net/u013568373/article/details/90543219