5.7.1. 事务介绍
- 数据库事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
- 事务的特性 ACID
- A是原子性 : 一个事务中的所有操作,要么全部完成要么全部不完成,不会结束在中间的某个环节 : 假如在执行过程中发生错误,会回滚(RollBack)到事务开始之前的操作
- C是一致性,意思是一个事物在执行之前和执行之后数据库必须处于一致性的状态
- I 是隔离性,指当 多个用户并发访问 数据库时,比如操作同一张表时,数据库为每个用户开启的事务,不能被其他事务的操作干扰,多个并发事务要互相隔离。 对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后开始,这样每个事务都感觉不到有其他的事务在并发执行。
- D是持久性,意思是假如事务执行成功结束,他对数据库的更新必须保存下来,即使系统发生崩溃,重新启动数据库后,也能回到事务成功结束时的状态
- 事务类型 : 编程式事务(自己写代码实现)、声明式事务(通过配置让框架实现)
5.7.2. 事务使用
- 基于注解的声明式事务 : 依赖,xml配置,Service层注解@Transactional
-
- @Transactional 标识在方法上,表示该方法加了事务
- @Transactional 标识在类上,表示该类中所有的方法都加了事务
- 事务属性介绍 ↓
-
- 超时 :事务执行过程中可能会遇到一些问题导致程序卡住长时间占用数据库资源,此时应该回滚出问题的程序,撤销已做的操作,结束事务让出资源。 -- 超时回滚,释放资源
- 回滚策略 : 声明式事务默认只对运行时异常回滚,编译时异常不回滚,可以通过 @Transactional设置回滚策略
- 假如不考虑 事务隔离性 会出现的四个问题:
-
- 脏读 : 脏读是指在一个事务处理过程里读取了另一个未提交事务中的数据。当一个事务正在多次修改某个数据,而在这个事务中这多次的修改都还未提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致
- 不可重复读 : 不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。
- 幻读 : 事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读
- 隔离级别 :四个,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,并发性越弱,
-
- 读未提交 :允许事务一读取事务二未提交的数据
- 读已提交 :要求事务一只能读取事务二已经提交了的内容
- 可重复读 :确保事务一能够多次从一个字段中读取到相同的值,也就是事务一执行期间禁止其他事务对这个字段更新
- 串行化 :确保事务一可以多次从一个表中读取到相同的数据,也就是事务一执行期间,其他事务禁止添加,更新,删除操作
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
-
- 传播行为 : 在service类中有 a() 方法和 b() 方法,a() 方法上有事务,b() 方法上也有事务,当 a() 方法执行过程中调用了 b() 方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。一共有七种传播行为:
-
- required:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
- supports:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
- mandatory:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
- requires_new:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
- not_support:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
- never:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
- nested:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像 REQUIRED 一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和 REQUIRED 一样。】
2.基于注解的声明式事务 :修改 Spring 配置文件:将 Spring 配置文件中去掉 tx:annotation-driven 标签,并添加配置:
<aop:config>
<!-- 配置事务通知和切入点表达式 -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.xszx.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method标签:配置具体的事务方法 -->
<!-- name属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- read-only属性:设置只读属性 -->
<!-- rollback-for属性:设置回滚的异常 -->
<!-- no-rollback-for属性:设置不回滚的异常 -->
<!-- isolation属性:设置事务的隔离级别 -->
<!-- timeout属性:设置事务的超时属性 -->
<!-- propagation属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>