0、判断有没有@Transctional注解
1、利用事务管理器建立一个数据库连接conn2, conn .autocommit = false 自动提交设为false
被代理的方法target.method(); 执行相应的sql3、conn . commit() 成功提交,否则回滚 rollback():
原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么一起失败。
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
Spring 无事务的方法调用有事务的方法不会开启新的事务!Spring采用动态代理的机制来实现事务控制,动态代理最终调用原始对象,原始对象调用方法时不会触发动态代理!
获取代理对象方法:
使用ApplicationContext上下文获取
AopContext.currentProxy() 需配置启动类开启 @EnableAspectJAutoProxy(exposeProxy = true)
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
传播属性 | 描述 | 补充说明 |
---|---|---|
REQUIRED | 如果存在事务,当前方法在这个事务中运行;如果不存在、开启自己的事务 | 需要、默认 |
SUPPORTS | 如果存在事务,当前方法在这个事务中运行;如果不存在、非事务的支持 | 支持 |
MANDATORY | 如果存在事务,当前方法在这个事务中运行;如果不存在则抛异常 | 必要的 |
REQUIRES_NEW | 总是开启新的事务,如果主方法事务存在,则挂起 | |
NOT_SUPPORTED | 总是非事务执行,存在事务则挂起 | |
NEVER | 总是非事务执行,存在事务则抛异常 | 绝不 |
NESTED | 如果有事务则嵌套,无则开启事务 | 嵌套 |
模拟 ——> 两个方法: 其中一个主方法,另一个子方法
方法头上加上注解、指定传播级别、数据库隔离级别
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.REPEATABLE_READ)
Spring的事务管理不需要与任何特定的事务API耦合(面向PlatformTransactionManager)
针对不同的持久层API,编程式事务提供一致的事务管理,通过模块化的一致性操作、管理事务;
隔离级别 | 脏读 | 不可重复读 | 幻读 | 是否默认 | 读取数据 |
---|---|---|---|---|---|
读未提交 READ-UNCOMMIT | 是 | 是 | 是 | 否 | 快照读和当前读:数据版本一样 |
读已提交 READ-COMMIT | 否 | 是 | 是 | 否 | 总读取最新的快照版本 |
可重复读 REPEATABLE-READ | 否 | 否 | 是 | 是 | 总读事务开启前的快照 |
序列化 SERIALIZABLE | 最高隔离,保证ACID |
可重复读 REPEATABLE-READ:
读未提交 READ-UNCOMMIT: 事务B会读到事务A未提交的修改!
幻读解决: 使用间隙锁
间隙锁是一个在索引记录之间的间隙上的锁
可重复读 REPEATABLE-READ默认使用
SQL语句使用WHERE关键字进行条件搜索,条件全部命中,加记录锁,否则,加间隙锁
可能会导致死锁 (间隙锁之间不是互斥的,如果一个事务A获取到了(5,10]之间的间隙锁,另一个事务B也可以获取到(5,10]之间的间隙锁。这时就可能会发生死锁问题,如: 事务A获取到(5,10]之间的间隙锁不允许其他的DDL操作,在事务提交(间隙锁释放)之前,事务B也获取到了间隙锁(5,10],这时两个事务就处于死锁状态)
间隙锁会用在非唯一索引或者不走索引的当前读中
间隙的范围 根据检索条件向下寻找最靠近检索条件的记录值A作为左区间,向上寻找最靠近检索条件的记录值B作为右区间,即锁定的间隙为(A,B)。
假设你有一组记录,其中age列的数据为:
age[18,19,19,20,22,22,22,22,30,31]
select * from t where age=22;那么间隙锁锁定的间隙为:(20,30),所以你再想插入20到30之间的数就会被阻塞。执行update t set age = 22 where id = 1会被阻塞。
要保证每次查询age=22的数据行数不变,如果你将另外一条数据修改成了22,岂不会多了一条?
show variables like "tx_isolation"
set tx_isolation = 'REPEATABLE-READ'
脏读 (事务A、事务B同时执行)
1、 事务A更新了某条记录
2、 事务B读取了事务更新的记录
3、事务A回滚
4、事务B读取到的记录无效
不可重复读
1、事务A读取了记录
2、事务B更新了事务A读取的记录,提交
3、事务A再次读取该记录(事务B修改的记录)
幻读
事务A读取事务B新增的数据(不符合隔离性)
1、事务A读取了表里的一部分数据
2、事务B向同一张表中增加了新的行
更新丢失
两个事务同时操作同一记录,后提交的事务会覆盖先提交的事务。
MVCC对普通的SELECT不加锁 (常规SELECT * FROM TABLE 语句),如果读到的数据在执行DELETE或UPDATE,这时,读不会等锁释放,直接利用MVCC读取改行数据的历史快照,基于undolog实现,undo log用来做事务回滚。
普通的SELECT <—— 快照读也叫非阻塞读,即所谓快照读就是不加锁的非阻塞读!
SELECT * FROM TABLE FOR UPDATE; FOR UPDATE 实现一个排它锁,事务结束前,其他事务不能更新该数据!
如果一个有事务的方法,内部再调用自己的另一个方法(方法被传播传播机制为NEVER),如果直接调用,不会报异常!此时要么拆成一个类,让该类的代理对象执行NEVER传播机制的方法。要么自己注入自己,自己执行自己的NEVER传播机制的方法,会报异常!
@AutoWired
private OrderService orderService;