Spring笔记(二)
Spring里的事务是怎么进行管理的?回答这个问题之前,我们先了解一下,事务的相关概念。
1.什么是事务,它有哪些特性?
事务是逻辑上的一组操作,要么都执行,要么都不执行,它有ACID四个特性:
- 原子性:事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用
- 一致性:执行事务前后,数据保持一致
- 隔离性:并发访问数据库时,一个用户的事务不受其他事务所干扰,各并发事务之间数据库是独立的
- 持久性:一个事务被提交之后。它对数据库的改变是永久性的,即使数据库发生故障也不应该对其有任何影响
2.怎样进行事务的管理呢?
Spring事务管理接口:
- PlatformTransactionManager:(平台)事务管理器
- TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
- TransactionStatus:事务运行状态
两种事务的管理方式:
-
编程式事务:在JavaSE时期,我们最常用的就是这种(因为也只会这种),上例子:
try{ /*业务代码*/ conn.commit(); }catch(Exception e){ conn.rollBack(); }
-
声明式事务:在Spring里面,人家就使用AOP帮我们进行了管理,我们只需在xml里配置就行,上例子:
<!-- 第一步:配置事务管理器 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 第二步:配置事务增强 --> <tx:advice id="txadvice" transaction-manager="dataSourceTransactionManager"> <!-- 做事务操作 --> <tx:attributes> <!-- 设置进行事务操作的方法匹配规则 --> <!-- account开头的所有方法 --> <tx:method name="account*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"rollback-for=""timeout="-1" /> </tx:attributes> </tx:advice> <!-- 第三步:配置切面 切面即把增强用在方法的过程 --> <aop:config> <!-- 切入点 --> <aop:pointcut expression="execution(* cn..*(..))" id="pointcut1" /> <!-- 切面 --> <aop:advisor advice-ref="txadvice" pointcut-ref="pointcut1" /> </aop:config>
后来,spring又提出来注解式事务,再一次简化了我们的配置,例子:
//xml配置文件中,需要启用注解事务 <tx:annotation-driven transaction-manager="transactionManager"/> //注解加到类上,表明该类下的所有方法都被事务进行管理 @Transactional(propagation = Propagation.REQUIRED)//配置事务的传播行为 public class OrdersService { public void accountMoney() { //业务代码 } }
3.事务的传播行为
简单点说,事务的传播行为就是事务的传递,一般分为7种:
- 支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
- 不支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
- 其他情况:
- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于第一种PROPAGATION_REQUIRED
4.事务的隔离级别
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的
REPEATABLE_READ隔离级别 ,Oracle 默认采用的 READ_COMMITTED隔离级别. - TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变
更,可能会导致脏读、幻读或不可重复读 - TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏
读,但是幻读或不可重复读仍有可能发生 - TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据
是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。 - TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻
读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
5.并发事务的带来的问题
- 脏读:当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
- 丢失修改:指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
- 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
- 不可重复读: 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读
- 幻读:幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
ISOLATION_READ_UNCOMMITTED(读取未提交) | √ | √ | √ |
ISOLATION_READ_COMMITTED(读取已提交) | × | √ | √ |
ISOLATION_REPEATABLE_READ(可重复读) | × | × | √ |
ISOLATION_SERIALIZABLE(可串行化) | × | × | × |
不可重复度和幻读区别:
不可重复读的重点是修改,幻读的重点在于新增或者删除。
例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资变为2000;这就是不可重复读。
例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。
6.总结
数据库的事务方面是非常终于的一块知识,尤其在并发操作中;虽然现在Spring帮我们进行了事务管理,特别是注解式声明,使得管理事务变得非常简单,只需在方法或者类里面添加@Transactional注解就完成了。
但是,如果我们自己不深入了解里面的原理和细节的话,一旦程序发生错误,排查bug将变得异常艰难,
最后,照例推上几篇干货: