前言
本章讲解Spring的声明式事务
方法
1.概念
我们之前学过了IOC/DI、AOP,接下来我们将学习Spring的最后一个知识点声明式事务。
事务的概念如下:
数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。
我们可以将事务简单的理解为一个service方法!!
事务的属性:
- 原子性(A):简单理解就是事务包含的所有操作要么全部成功,要么全部失败回滚
- 一致性(C):简单理解就是事务前和事务后的都必须处于一致性状态,比如两个人互相转账,不论多少次它们的金额总和是不变的
- 隔离性(I):简单理解就是多个并发事务之间要相互隔离,绝对不能同时操作
- 持久性(D):简单理解就是一旦事务提交,对数据库中的数据的改变就是永久性的
说到隔离性,每个数据库都有自己的默认的事务隔离级别
如果我们不考虑事务的隔离,多事务并发将造成如下三种问题:
- 脏读:指在一个事务处理过程里读取了另一个未提交的事务中的数据。比如A正在给B进行汇款500元,但是A没有提交事务,那么当B进行查看的时候查看的是内存中的数据,以为是汇款成功。实际上并没有汇款成功!
- 不可重复读:指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值(出现于修改某一数据)。比如A正在查询自己的银行卡余额5000,此时A的老婆从ATM取出3000,这时候A要求取出3000的时候发现钱变成了2000!!!
- 幻读:指对于数据库某张表的数据,一个事务范围内多次查询却返回了不同的数据量(出现于新增、删除某表数据)。比如A在统计工资收入情况,发现3000以下的人有三个,准备要进行打印的时候再次查询发现出现了四个,感觉像是产生了幻觉!!!
当然了,数据库早就为我们提供好了事务的隔离级别来回避上述问题,这个我们后面再谈。
2.使用Spring配置声明式事务
1)编程式事务
我们知道,在之前的JDBC阶段或者MyBatis阶段,我们进行事务的控制都是在代码中进行的,比如commit和rollback等操作
编程式事务就是由我们程序员自己编写事务控制代码,这显然是麻烦的,所以Spring为我们提供了其声明式事务,我们仅仅需要进行相关的配置,就能够在需要添加事务控制的代码上添加事务控制!
2)声明式事务
声明式事务的原理依托我们之前所学的AOP,即方法前开启事务,方法后执行相关事务操作
特别的:这里要知道我们的事务都是加在service方法的,原因大家想一想是为什么?
配置步骤:
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" >
<property name="driverClassName" value="${jdbc.driverClass}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 配置事务管理器 -->
<bean id="tx" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务通知,方便告诉spring在哪些方法添加事务管理 -->
<tx:advice id="txAdvice" transaction-manager="tx">
<tx:attributes>
<tx:method name="listAll"/>
<tx:method name="insert"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面,告诉spring声明式事务的切入点 -->
<aop:config>
<aop:pointcut id="myPoint" expression="execution(* cn.edu.ccut.service.impl.*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPoint"></aop:advisor>
</aop:config>
这样的话,最简单的声明式事务就配置完成了,其中的listAll、insert方法我们为它们添加了事务管理。
3.声明式事务的属性解释
tx:method标签存在一些属性,我们对这些属性进行解释说明
1)name:表示哪些方法需要有事务控制,可以配置通配符*,如ins*表示所有以ins开头的方法需要配置事务
2)readonly:可选值为true/false,它用来告诉数据库该事务是否为只读事务,默认为false
3)propagation:可选值为REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED
其表示事务的传播行为,即一个事务在另一个事务中的时候所进行的操作。
- REQUIRED(default):当前有事务就在当前事务执行,如果当前没有事务就新建一个事务。默认选项
- SUPPORTS:当前有事务就在当前事务执行,如果当前没有事务,就以非事务方式执行。
- MANDATORY:当前有事务就在当前事务执行,如果当前没有事务,就抛出异常。
- REQUIRES_NEW:如果当前没有事务就新建一个事务,如果当前存在事务,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
- NESTED:如果当前事务存在,则执行一个嵌套事务,如果当前没有事务,就新建一个事务。
4)isolation:可选值为ReadUncommitted、ReadCommitted、RepeatableRead、Serializable
- ReadUncommitted:未提交读。当事务A更新某条数据的时候,不容许其他事务来更新该数据,但可以进行读取操作
- ReadCommitted:提交读。当事务A更新数据时,不容许其他事务进行任何的操作包括读取,但事务A读取时,其他事务可以进行读取、更新(防脏读)
- RepeatableRead:重复读。当事务A更新数据时,不容许其他事务进行任何的操作,但是当事务A进行读取的时候,其他事务只能读取,不能更新(防脏读、不可重复读)
- Serializable:序列化。最严格的隔离级别,当然并发性也是最差的,事务必须依次进行。(防脏读、不可重复读、幻读)
特别的:Oracle的默认隔离级别为ReadCommitted ,MySQL为RepeatableRead
5)rollback-for:需要填写异常的完整类路径,表示出现什么异常时数据库事务进行回滚,默认是java.lang.Exception
6)no-rollback-for:表示出现什么异常时不需要事务回滚