vlambda博客
学习文章列表

面试重点-spring事务管理的全面了解

transaction是什么?

transaction,是指一组数据库操作,必须全部执行,或者全部不执行。

一、数据库并发操作引发的问题

1、脏读(Dirty read)一个事务修改了数据,还未提交。此时,另一个事物读取了这条数据,就成了脏数据。因为第一条事务有可能回滚。


2、丢失修改 (Lost modify) 存在两种情况:

第一种丢失修改,事务1和事务2都读到一条数据金。事务1更新并提交成功,接着事务2更新失败回滚。那么事务2会把事务1更新的数据还原到最初始值,导致事务1的数据丢失;
第二种丢失修改,事务1和事务2都读到一条数据,并更新了同一条数据。事务1更新并提交成功,接着事务2也更新提交。那么事务2更新值会覆盖事务1的,导致事务1的数据丢失。


3、不可重复读(Unrepeatable Read)事务1两次读取某条数据,两次读取之间有事务2更新了这条数据,将出现两次读取的数据不一致。


4、幻读( Phantom Read) 事务1两次或者多次读取某一范围的数据,这期间有另一个事务2新增或删除了数据,将出现前后读取的数据数量不一致。不可重复度和幻读的区别在于:不可重复读是修改操作,幻读是新增和删除操作。

二、数据库事务特性数据库事务的特点:  

1、原子性( Atomicity) 是指事务中的操作必须全部保存,或者全部回滚。比如,银行转账交易中,从账户A转账到账户B,那么要从账户A扣除金额,在账户B加入相应的金额,这两个操作要么都成功,要么都不成功。  


2、一致性( Consistency涉及两个方面:

一方面, This means that a transaction should change the database from one consistent state to another.事务执行前后,数据库由一种状态迁移到另一种状态。比如上述账户A转账到账户B,A转出的金额必须跟转入B的金额一致。
另一方面,事务提交的数据必须与数据库的完整性约束一致。  


3、隔离性( Isolation 多个事务间先互独立,互不干扰。也就是说事务1未提交前,事务1中修改的数据,其他事务看不到这个修改。  


4、持久性( Durability)  一当事务完成了,比如被保存到数据库中,即便数据库崩溃也不能丢失。在这几个特性中,一致性是目的,AID都是为了达成C的手段。

三、数据库事务隔离级别

        数据库专家们为了解决数据库事务相互干扰的引起的数据问题,给事务制定了一些规范,将它们分隔开来。于是,就有了数据库隔离级别。


1、读未提交 Read Uncommitted       一个事务还没提交的数据,另一个事务中可以读取到。这种情况,最容易出错,毫无安全性可言。    

事务1 mysql> begin; mysql> update account set amount=999 where id=1;事务2:mysql>set @@session.tx_isolation='read-uncommitted';#设置为读未提交 mysql> begin; mysql> select * from account where id=1; +----+------+--------+ | id | name | amount | +----+------+--------+ | 1 | jim | 999 | +----+------+--------+事务2 读取到事务1未提交的脏数据。



2、读已提交 Read Committed    一个事务更新的数据提交后,另一个事务才能读取到此次更新。存在问题是不可重复读

set @@session.tx_isolation='read-committed';事务1:mysql> select @@session.tx_isolation;+------------------------+| @@session.tx_isolation |+------------------------+| READ-COMMITTED |+------------------------+事务1:mysql> select * from account where id=1;+----+------+--------+| id | name | amount |+----+------+--------+| 1 | jim | 1999 |+----+------+--------+事务2mysql> begin;mysql> update account set amount=2000 where id=1;mysql> commit;事务1mysql> select * from account where id=1;+----+------+--------+| id | name | amount |+----+------+--------+| 1 | jim | 2000 |+----+------+--------+事务1两次读取的数据不一样

3、可重复读  Repeatable Read    保证了一个事务多次读取同一数据内容是一样的,不受其他事务的影响。


4、串行化 Serializable    这是最严格的隔离级别,性能最差。所有的事务都是串行的,也就不会出现并发。比如一个事务读取数据时,不允许其他事务进行更新操作。Mysql  默认级别是 Repeatable read,Oracle和Sql Server 默认都是Read Committed。(mysql的可重复读,通过MVCCMulti-Version Concurrency Control),解决了幻读的问题)下面汇总一下各种隔离级别在事务并发问题处理方面的表现:


脏读 丢失修改 不可重复读 幻读
读未提交 允许 允许 允许 允许
读已提交 禁止 禁止第一类,允许第二类 允许 允许
可重复读 禁止 禁止 禁止 允许
串行化 禁止 禁止 禁止 禁止

四、 JDBC事务提供的方法


#关闭自动提交connection.setAutoCommit(false);connection.setTransactionIsolation(Connection.TRANSACTION_NONE);#回滚connection.rollback();#提交connection.commit();


五、spring 事务管理机制

如图中所示,spring事务管理是基于jdbc事务机制,而jdbc事务机制是基于数据库事务特性设计的。 

1、spring事务接口


  • PlatformTransactionManager 事务管理器

主要实现类及其对应数据访问技术    

JDBC DataSourceTransactionManager
JPA JpaTransactionManager
Hibernate HibernateTransactionManager
JTA JtaTransactionManager
  • TransactionDefinition 事务定义信息(事务隔离级别、传播机制、超时、只读、回滚规则)

      


  • TransactionStatus 事务运行状态

    2、spring事务传播机制

REQUIRED: 当前方法需要在事务上下文中执行,如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

举例:方法A调用方法B,方法B的传播方式为REQUIRED若方法A调用方法B,方法A已开启事务,则方法B不需要开启新事务,存在以下三种情况   

1、 若B发生异常,A未捕获,则事务A和B都回滚    
2 、若B发生异常,A捕获了这个异常,则事务A和B回滚且抛出 Transaction rolled back because it has been marked as rollback-only    
3 、若B正常执行,而A发生异常,则A和B都回滚若方法A未开启事务,则新建一个事务给方法B。 

SUPPORTS:如果当前存在事务,则加入该事务,在此事务下执行;如果当前没有事务,则以非事务的方式继续运行。

举例:方法A调用方法B,方法B的传播方式为SUPPORTS若方法A启用事务,则方法B在此事务中执行,存在以下三种情况:    

1、 若B发生异常,A未捕获,则事务A和B都回滚   
2 、若B发生异常,A捕获了这个异常,则事务A和B回滚且抛出 Transaction rolled back because it has been marked as rollback-only    
3 、若B正常执行,而A发生异常,则A和B都回滚
若方法A未开启事务,则方法B在非事务环境中执行。

MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

举例:方法A调用方法B,方法B的传播方式为MANDATORY

若方法A未开启事务,则方法B会抛出异常:No existingtransaction found for transaction marked with propagation 'mandatory';

若方法A开启事务,存在以下三种情况:   

1、 若B发生异常,A未捕获,则事务A和B都回滚    
2 、若B发生异常,A捕获了这个异常,则事务A和B回滚且抛出 Transaction rolled back because it has been marked as rollback-only    
3 、若B正常执行,而A发生异常,则A和B都回滚

REQUIRES_NEW: 每次都会创建一个新的事务,如果当前存在事务,则把当前事务挂起。

举例:方法 A调用方法B,方法B的传播方式为REQUIRES_NEW

若方法A未开启事务,则方法B开启一个新事务

若方法A已开启事务,则挂起方法A的事务,给方法B新启一个新事务。存在以下三种情况   

1、方法A异常回滚,方法B的事务不受影响。
2、方 法B异常,A未捕获,事务A和B都回滚
3、方 法B异常,A捕获了这个异常,则方法A不受影响,方法B回滚 NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
举例:  方法A调用方法B,方法的传播方式为NOT_SUPPORTED
若方法A已开启事务,则存在以 下情况:     
1、方法B发生异常,A未捕获,则A事务回滚,B在异常点之前的操作不受影响;

 2、方法B发生异常,A捕获异常,则A事务不回滚,B在异常点之前的操作不受影响。

NEVER 以非事务方式运行,如果当前存在事务,则抛出异常。
举例: 方法A调用方法B,方法B传播方式为NEVER
若方法A开启事务,则方法B抛出 Existingtransaction found for transaction marked with propagation 'never'

NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行; 如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
举例: 方法A调用方法B,方法B传播方式为NESTED
若方法A开启事务,则使用jdbc的保存点将事务A保存起来,然后为方法B新建一个事务嵌入到事务A中。 存在以下几种情况:      

1、事务A发生异常,则事务A和B都回滚    

2、事务B发生异常,A未捕获异常,则A和B都回滚;    

3、事务B发生异常,A捕获了异常,则A不回滚,B回滚。

六、事务失效问题

        spring事务管理,在以下情况会失效。

1、内部方法间调用导致事务失效,比如非事务方法A调用同一个类的带@Transactional的方法B,方法B的事务执行时没有开启,这是因为同一个类中的方法调用不需要通过代理,而是直接调用。

spring的事务默认模式是通过代理AdviceMode.PROXY

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Import({TransactionManagementConfigurationSelector.class})public @interface EnableTransactionManagement { boolean proxyTargetClass() default false; AdviceMode mode() default AdviceMode.PROXY; int order() default 2147483647;}

     

解决方法是把模式改为AdviceMode.ASPECTJ 或者将方法B放到另一个类中2、入口函数不是public的

3、数据库引擎不支持事务(例如Mysql的MyIsam不支持事务) ,行锁才支持事务