面试重点-spring事务管理的全面了解
transaction是什么?
transaction,是指一组数据库操作,必须全部执行,或者全部不执行。
一、数据库并发操作引发的问题
1、脏读(Dirty read)一个事务修改了数据,还未提交。此时,另一个事物读取了这条数据,就成了脏数据。因为第一条事务有可能回滚。
2、丢失修改 (Lost modify) 存在两种情况:
3、不可重复读(Unrepeatable Read)事务1两次读取某条数据,两次读取之间有事务2更新了这条数据,将出现两次读取的数据不一致。
4、幻读( Phantom Read) 事务1两次或者多次读取某一范围的数据,这期间有另一个事务2新增或删除了数据,将出现前后读取的数据数量不一致。不可重复度和幻读的区别在于:不可重复读是修改操作,幻读是新增和删除操作。
二、数据库事务特性数据库事务的特点:
1、原子性( Atomicity) 是指事务中的操作必须全部保存,或者全部回滚。比如,银行转账交易中,从账户A转账到账户B,那么要从账户A扣除金额,在账户B加入相应的金额,这两个操作要么都成功,要么都不成功。
2、一致性( Consistency)涉及两个方面:
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 |
+----+------+--------+
事务2:
mysql> begin;
mysql> update account set amount=2000 where id=1;
mysql> commit;
事务1:
mysql> 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不需要开启新事务,存在以下三种情况
SUPPORTS:如果当前存在事务,则加入该事务,在此事务下执行;如果当前没有事务,则以非事务的方式继续运行。
举例:方法A调用方法B,方法B的传播方式为SUPPORTS若方法A启用事务,则方法B在此事务中执行,存在以下三种情况:
若方法A未开启事务,则方法B在非事务环境中执行。
MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
举例:方法A调用方法B,方法B的传播方式为MANDATORY
若方法A未开启事务,则方法B会抛出异常:No existingtransaction found for transaction marked with propagation 'mandatory';
若方法A开启事务,存在以下三种情况:
REQUIRES_NEW: 每次都会创建一个新的事务,如果当前存在事务,则把当前事务挂起。
举例:方法 A调用方法B,方法B的传播方式为REQUIRES_NEW
若方法A未开启事务,则方法B开启一个新事务
若方法A已开启事务,则挂起方法A的事务,给方法B新启一个新事务。存在以下三种情况
2、方法B发生异常,A捕获异常,则A事务不回滚,B在异常点之前的操作不受影响。
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不支持事务) ,行锁才支持事务