说明:本篇文章的代码是基于上篇文章之上的,如有需要,请移至这篇文章
声明式事务的属性:只读,超时,回滚策略:
事务属性:只读
对一个查询操作来说,如果我们把它设置为只读,就能明确的告诉数据库,这个操作不涉及写操作,这样数据库就能够针对查询操作来进行优化
设置方法:
@Transactional(readOnly = true)
大家注意看该属性值的默认值为false,那么也就是说,只有在事务中的操作都为查询操作时,才需要将其设置为只读
注意:只有当事务中的操作都为查询操作时,才能设置事务的只读操作,而如果事务中出现了其他的操作,我们依然将其设置为只读,就会报错
举例:当我们在包含除查询以外的其他操作的事务中,将该事务设置为只读:
报错如下所示:
事务属性:超时
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源,而长时间占用资源,大概率是因为程序运行出现了问题(可能是java程序或者mysql数据库或网络连接等等)
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行
概括来说就是一句话:超时回滚,释放资源
设置方法:
//将当前的事务执行时间设置为3秒钟,如果超过三秒钟,则直接抛出异常 @Transactional(timeout = 3)
举例:
报错如下:事务超时异常
事务属性:回滚策略
声明式事务默认只针对运行时异常回滚,编译时异常不回滚
可以通过@Transactional中相关属性设置回滚策略
//通常情况下,第一二种我们是不进行设置的,因为所有的运行时异常都会被回滚 rollbackFor属性:需要设置一个Class类型的对象 rollbackForClassName属性:需要设置一个字符串类型的全类名 noRollbackFor属性:需要设置一个Class类型的对象 rollbackFor属性:需要设置一个字符串类型的全类名
举例:
更新表中的数据,使其余额大于图书价格,如下所示:
第二步:在购买图书的方法末,手动添加运行时异常,如下所示:
报错如下:报错内容似乎并没有关于图书购买失败的信息,只抛出了我们手动设置的运行时异常,那么这是代表图书购买成功了吗?
在数据库中进行查询,发现余额和图书的库存量都没有发生改变,原因是:声明式事务默认只针对运行时异常回滚,我们并没有设置其值,那么就是处于默认情况下
但上述的数学运算异常好像和我们要处理的业务并没有关系,由此我们可以通过回滚策略进行处理
方法1:
如下所示:
@Transactional(noRollbackFor = {ArithmeticException.class})
noRollbackFor的类型为数组类型[如下所示],因此当其值为多个时,必须使用{}包含,如果其值只有一个,则"{}"可省略
再次运行结果如下所示:
好像和上面的结果并没有什么不同,我们再去数据库进行数据的查询,如下所示,图书购买成功,原因是:我们通过@Transactional(noRollbackFor = {ArithmeticException.class})设置当运行时异常为数学运算错误时,不进行回滚操作,因此事务没有回滚
方法2:
@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
由于noRollbackForClassName的类型为字符串,因此,它的值为该异常的全类名
这种方法的产生效果和第一种完全相同,这里就不演示了
注意:在进行测试时,必须要满足图书是能够被成功购买的条件,因为如果图书都无法被成功购买,那么代码将无法运行到方法末的数学运算
声明式事务的属性之隔离级别:
数据库系统必须具有隔离并发运行各个事务
的能力,使它们不会相互影响,避免各种并发问题,一个事务与其他事务隔离的程度
称为隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱
隔离级别一共有四种:
读未提交:READ UNCOMMITTED 允许Transaction01读取Transaction02未提交的修改 读已提交:READ COMMITTED 要求Transaction01只能读取Transaction02已提交的修改 可重复读:REPEATABLE READ 确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其他事务对这个字段进行更新 串行化:SERIALIZABLE 确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其他事务对这个表进行添加,更新,删除操作,可以避免任何并发问题,但性能十分低下
各个隔离级别解决并发的能力:
各种数据库产品对事务隔离级别的支持程度:
设置属性的隔离级别:
方法:
//一般将其设置为默认值 @Transactional(isolation = Isolation.DEFAULT)
打开Isolation的源码,如下所示,Isolation是一个枚举类型
声明式事务的属性之传播行为:
什么是时候之间的传播行为呢?举例来说就是假设现在有两个方法A和B,这两个方法都加上了@Transactional注解,此时这两个方法都有事务的功能,假设现在A事务方法去调用B事务方法,但由于AB两个方法都有事务的功能,因此当A方法调用B事务方法的这个过程中,就会将A方法中的事务传递给B方法,这就造成了B方法现在既有自己本身的事务功能,还有A事务的事务功能,它在实行功能的时候,既可以实行自己本身的额,也可以实行A传递给它的
下面我们就通过具体的实例来演示该功能:
创建实现买单功能的接口:
package spring_txAnnotation.Service; public interface CheckOutService { void Checkout(Integer userId, Integer[] bookId); }
创建实现买单功能的接口的实现类:
package spring_txAnnotation.Service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import spring_txAnnotation.Controller.BookController; @Service public class CheckOutServiceImpl implements CheckOutService{ @Autowired private ServiceBook serviceBook; @Autowired private CheckOutService checkOutService; @Autowired private BookController bookController; public void buyBook(Integer userId,Integer bookId){ serviceBook.buyBook(userId,bookId); } @Transactional @Override public void Checkout(Integer userId, Integer[] bookId) {//结账方法 for(Integer bookIds:bookId){ serviceBook.buyBook(userId,bookIds); } } }
将新功能的实现代码加入Controller层:
@Autowired private CheckOutService checkOutService; public void Checkout(Integer userid, Integer[] integers) { checkOutService.Checkout(userid,integers); }
测试类如下所示:
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import spring_txAnnotation.Controller.BookController; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:TXannotation.xml") public class Test1 { @Autowired private BookController bookController; @Test public void testBuyBooks(){ bookController.Checkout(1,new Integer[] {1,2}); } }
我们为其新添加了一个事务之后,就导致当前的组件含有两个事务功能,一个是买书操作,一个是结账功能,如下所示:
在运行之前,我们先对数据库中的数据进行查询:
如下所示:
当前用户的余额为100元,只能满足购买一本书的条件,因此,如果执行的是结账操作,那么意味着将购买两本书,显然当前的用户余额是无法满足的,那么就会出现两本书都无法被购买的情况,下面我们运行该程序
报错如下所示:
此时我们再次访问数据库中的数据,如下所示:
用户余额和图书库存量都没有发生任何的改变,由此可说明当前执行的是结账的事务功能,因为只要有一本书无法购买,会导致整个事务回滚到最初的状态
那么如果我们想设置使之执行的是本身的买书事务功能呢?
方法:通过@Transactional中的propagation属性设置事务传播行为
打开该属性的源码,如下所示,我们发现该属性的默认值是Propagation.REQUIRED
,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行
经过观察,购买图书的方法buyBook0在checkout0中被调用[如下所示],checkout0上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了
而当我们将@Transactional中的propagation属性的值设置为Propagation.REQUIRES_NEW,表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook0中回滚,购买第一本图书不受影响,即能买几本就买几本
设置完毕之后,再次运行,数据库中的数据如下所示: