一、事务的传播
事务的方法相互调用,事务在这些方法间传播。
二、spring事务传播类型
1. 用法:
@Transactional 使用枚举定义事务的传播类型
如,@Transactional(propagation=Propagation.REQUIRED)
注:Spring中默认采用AOP代理,同一个Service中调用,要采用注入的方式来调用,如果在使用this.方法名,
对象内部方法调用,不会通过spring代理,事务无效。
2. 7种类型:
1.REQUIRED --> 如果当前没有事务,则自己新建一个事务;如果当前存在事务,加入到testMain当前事务中
(示例一)预期:数据库数据没有变化,testMain声明了事务,testB执行时加入到当前事务中,testB回滚导致主方法也回滚。
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
B(b1); //调用B入参b1
throw Exception; //发生异常抛出
B(b2); //调用B入参b2
}
(示例二)预期:testMain当前没有事务,testB有声明事务且传播行为REQUIRED,自己新建事务,A表添加成功,B回滚
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.REQUIRED)
public void testB(){
B(b1); //调用B入参b1
throw Exception; //发生异常抛出
B(b2); //调用B入参b2
}
2. SUPPORTS --> 当前存在事务,就加入到当前事务;如果当前没有事务,就以非事务方法运行
(示例)预期:testMain没有事务,所以默认testB也没有事务,a1,b1入库成功;如果testMain加入REQUIRED,当前存在事务,则加入当前事务中,a1,b1,b2入库均失败。
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.SUPPORTS)
public void testB(){
B(b1); //调用B入参b1
throw Exception; //发生异常抛出
B(b2); //调用B入参b2
}
3. MANDATORY --> 当前存在事务,就加入当前事务;没有事务,就抛出异常。
(示例)预期:a1入库成功,testMain没有声明事务,抛出异常
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.MANDATORY)
public void testB(){
B(b1); //调用B入参b1
throw Exception; //发生异常抛出
B(b2); //调用B入参b2
}
4. REQUIRES_NEW --> 创建一个新事物,当前存在事务,将事务挂起
(示例)预期:testMain抛出异常,testB开启新事务,不影响提交,b1,b2入库成功
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
throw Exception; //发生异常抛出
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void testB(){
B(b1); //调用B入参b1
B(b2); //调用B入参b2
}
5. NOT_SUPPORTED --> 始终以非事务方法执行,如果当前存在事务,将事务挂起
(示例)预期:testB不使用事务,b1入库成功,然后抛出异常,testMain检测异常,回滚数据,a1入库失败
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void testB(){
B(b1); //调用B入参b1
throw Exception; //发生异常抛出
B(b2); //调用B入参b2
}
6.NEVER 不使用事务,存在事务,抛出异常
(示例)预期:testB事务传播类型NEVER,testMain运行在事务中,testB不会执行抛出异常,testMain检测到异常,数据回滚,不会有数据入库
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
}
@Transactional(propagation = Propagation.NEVER)
public void testB(){
B(b1); //调用B入参b1
B(b2); //调用B入参b2
}
7.NESTED 如果当前事务存在,则在嵌套事务中执行,否则和REQUIRED一样
(示例)预期:和REQUIRES_NEW对比,REQUIRES_NEW是开启一个新事务,与原事务无关,NESTED子事务也会回滚,所有数据都不会入库
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
testB(); //调用testB
throw Exception; //发生异常抛出
}
@Transactional(propagation = Propagation.NESTED)
public void testB(){
B(b1); //调用B入参b1
B(b2); //调用B入参b2
}
(示例)预期:和REQUIRED对比,使用子事务,仅testB方法回滚,a1,a2存储成功,catch子事务回滚了;如果是REQUIRED所有数据均回滚,就算在catch中,整个事务还是会回滚,因为调用方和被调用方公用的一个事务
@Transactional(propagation = Propagation.REQUIRED)
public void testMain(){
A(a1); //调用A入参a1
try{
testB(); //调用testB
}catch(Exception e){
}
A(a2);
}
@Transactional(propagation = Propagation.NESTED)
public void testB(){
B(b1); //调用B入参b1
throw Exception; //发生异常抛出
B(b2); //调用B入参b2
}
三、事务机制
1.脏读(Dirty Read)
在事务A执行的过程中,发生了更改(update),事务B读取了A未提交的数据;此刻,事务A因为某些原因发生回滚Rollback,事务B读取的数据就是脏数据。
2.不可重复读(Nonrepeatable Read)
事务B读取了2次数据,2次结果是不一样,在读取两次的过程中事务A发生了修改,导致读取出来的数据不一致,同一个事务中,读取两次的结果是不一样的就是不可重复读。
3.幻读
事务B前后两次读取同一个范围的数据,在事务B读取的过程中A增加了数据,导致B后一次读取查询中之前没有的行;
和不可重复读有些类似,幻读强调的是事务A增加的记录,不可重复读强调的是修改的数据。
4.第一类更新丢失
事务A和事务B都对数据进行更新,A发生异常,进行回滚,把B提交的事务覆盖了。
5.第二类更新丢失
事务A和事务B都对数据进行更新,事务A把事务B的更新数据的给覆盖了
四、事务隔离级别
主流的关系型数据库,事务的隔离级别从低到高:读为提交,读已提交,可重复读,串行化;隔离级别设定越高,越能保持数据的一致性,执行效率就越低,mysql默认是可重复读
读未提交:最低的事务隔离级别,只能解决第一类数据更新丢失
读已提交:可以防止脏读和第一类数据丢失
可重复读:一个事务多次读同一个数据,没有结束时,其他事务不能访问该数据,可能会出现幻读
串行化:事务一个一个执行,大量超时现象和锁竞争