事务及Spring事务传播机制

发布时间:2023-04-15 11:30

Spring事务

​ --洱涷zZ


事务的四大特征

  1. 原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败
  2. 持久性:如果事务一旦提交或者回滚,那么数据库表的数据将被持久的更新
  3. 隔离性:多个事务之间相互独立,但是真实情况下,多个事务之间会产生影响
  4. 一致性:事务操作前后,数据总量不变

事务的隔离级别

​ 多个事务之间是是隔离的,相互独立的,如果多个事务操作同一批数据,则会引发一些问题,设置不同的隔离级别,就可以解决这些问题。

​ 存在问题:

  1. 脏读:一个事务读取到另一个事务没有提交的数据

    ​ 栗子:问李四接了500,李四将事务隔离级别设置成读未提交,对张三进行转账后但是没有提交,张三查看自己确实多了500,然后给李四写了借条,李四收到借条并将数据回滚,张三的500又回到李四账户,张三还得给李四还500。

  2. 不可重复读(虚读):在同一个事务中,两次读取到的数据不一样

  3. 幻读:一个事务操作(DML)数据表中的所有记录,另一个事务添加了一条数据,则第一个事务查询不到自己的修改。

​ 隔离级别:

​ 1.read uncommitted:读未提交

​ 产生的问题:脏读,虚读,幻读

​ 2.read committed:读已提交(Oracle默认的)

​ 产生的问题:虚读,幻读

​ 3.repeatable read:可重复读(MySQL默认的)

​ 产生的问题:幻读

​ 4.serializable:串行化(其实就是锁表 当一个事务在操作这个表的时候,其他事务是无法操作这个表的)可以解决所有的问题。

​ 注意:隔离级别从小到大 安全性越来越高,但是效率越来越低

事务注解@Transactional

​ 事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。

​ spring支持编程式事务管理和声明式事务管理两种方式。

编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

​ 当@Transactional作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

​ 在项目中,@Transactional(rollbackFor=Exception.class),如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。

​ 在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚

​ @Transactional属性:

属性 类型 描述
value String 可选的限定描述符,指定使用的事务管理器
propagation enum: Propagation 可选的事务传播行为设置
isolation enum: Isolation 可选的事务隔离级别设置
readOnly boolean 读写或只读事务,默认读写
timeout int (in seconds granularity) 事务超时时间设置
rollbackFor Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
rollbackForClassName 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
noRollbackForClassName 类名数组,必须继承自Throwable 不会导致事务回滚的异常类名字数组

事务的传播机制

​ 在Spring中对于事务的传播行为定义了七种类型分别是:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED

​ Spring中事务的默认实现使用的是AOP,也就是代理的方式。

先创建一个事务应用的场景

public void testMain(){
    A(a1);
    testB();
}

public void testB(){
    B(b1);
    throw Exception;
    B(b2);
}

​ 如果说给这两个方法都没有添加事务,那么此时运行testMain()方法,会发生什么情况呢,a1,b1分别存入对应的数据库表tableA1,tableB1,然后抛出异常,程序停止执行,b2不会被插入tableB2

REQUIRED

此种事务传播类型是Spring的默认事务传播类型

意思是:如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入

举栗子:

@Transational(propagation=Propagation.REQUIRED)
public void testMain(){
    A(a1);
    testB();
}

@Transational(propagation=Propagation.REQUIRED)
public void testB(){
    B(b1);
    throw Exception;
    B(b2);
}

​ 此场景下,此时数据库表数据为空,如果执行testMain(),先插入a1,然后插入b1,然后方法抛出异常后,事务发生回滚,因为testMain已经有事务,所以testB就直接加入了testMain的事务,那么当前testMain和testB是同一个事务,testB抛出异常事务回滚,testMain也会发生事务回滚,那么此时数据库表还是为空,所有数据都没有插入。

再举个栗子:

public void testMain(){
    A(a1);
    testB();
}

@Transational(propagation=Propagation.REQUIRED)
public void testB(){
    B(b1);
    throw Exception;
    B(b2);
}

​ 此场景下,testB有事务,testMain没有事务,那么在调用testMain时,testB判断testMain没有事务后自己就会新建一个事务,此时调用testMain,在testB抛出异常后,testB会发生回滚,而testMain就不会,所以此时a1就会被插入,b1会插入后因为事务回滚消失,b1,b2都不会被插入。

REQUIRES_NEW

意思是:创建一个新事务,如果存在当前事务,则挂起该事务

举栗子:为了说明requires_new会开启一个新事务,我把异常的位置放在testMain,然后给testMain声明事务,传播类型为required,testB也设置事务,传播方式为requires_new,如下

@Transational(propagation=Propagation.REQUIRED)
public void testMain(){
    A(a1);
    testB();
    throw Exception;
}
@Transational(propagation=Propagation.REQUIRES_NEW)
public void testB(){
    B(b1);
    B(b2);
}

​ 此时调用testMain,a1不会被插入,b1,b2会被插入,因为testMain和testB是两个事务,testMain抛出异常之后,事务回滚并不会影响到testB。
​ 与该场景相对应的就是两个传播方式都是REQUIRED,那么上面执行时,数据都不会被存储,因为是同一个事务,事务发生回滚时,所有数据都会发生回滚。

​ 如果说异常抛出点还是在testB的b1插入后,那么这个栗子,运行后结果是a1,b1,b2都不会被存储成功,此时并不是因为是一个事务所以一回滚都回滚,而是testB抛出异常后,testMain检测到它调用的方法出现异常,那么它也会回滚。

SUPPORTS

意思是:如果当前存在事务,那么就加入该事务,如果不存在事务,就以非事务方法执行

举栗子:

public void testMain(){
    A(a1);
    testB();
}
@Transational(propagation=Propagation.SUPPORTS)
public void testB(){
    B(b1);
	throw Exception;
    B(b2);
}

​ 此时只在testB上声明事务传播机制为SUPPORTS,testMain无事务,当调用testMain时,a1存储,然后调用testB,testB判断testMain无事务,所以它也会按照无事务方法执行,结果就是a1,b1都被存入数据库,b2没有存入,因为抛出异常时,程序已停止运行,b2未被执行。

​ 那么当我们在testMain上声明事务传播机制为REQUIRED,testB上声明事务传播机制为SUPPORTS,此时testB就会加入testMain的事务,最终结果就是a1,b1,b2都没有被存储。

NOT_SUPPORTED

意思是:始终以非事务方式执行,如果当前存在事务,则挂起该事务

​ 可以理解为设置事务传播类型为NOT_SUPPORTED的方法,在执行时,不管调用它的方法事务传播机制是啥,它都会按照非事务方法执行

举个栗子:

@Transational(propagation=Propagation.REQUIRED)
public void testMain(){
    A(a1);
    testB();
}
@Transational(propagation=Propagation.NOT_SUPPORTED)
public void testB(){
    B(b1);
    throw Exception;
    B(b2);
}

​ 调用testMain,最终结果为只有b1存储成功,因为testB是按照非事务方法执行,所以b1存储成功,b2未存储,此时testMain事务传播机制为REQUIRED,其检测到调用有异常被抛出,因为testMain和testB不是一个事务,所以只有testMain会发生回滚,a1也不会被存储成功。

MANDATORY

意思是:如果当前存在事务,那么就加入,如果当前事务不存在,则抛出异常

举个栗子

public void testMain(){
    A(a1); 
    testB();   
}
@Transactional(propagation = Propagation.MANDATORY)
public void testB(){
    B(b1);  
    throw Exception;   
    B(b2); 
}

​ 这种场景下最终结果为a1插入成功,b1,b2没有插入,此时未插入并不是因为testB数据回滚,而是因为testMain没有声明事务,所以testB会直接抛出事务要求的异常,所以testB中的方法就没执行

​ 如果testMain声明了事务,且设置为REQUIRED,那么在调用testB时就会正常的数据回滚,a1,b1,b2都不会被存储。

NEVER

意思是:不使用事务,如果当前事务存在则抛出异常

很容易理解,就是我这个方法不使用事务,并且调用我的方法也不允许有事务,如果调用我的方法有事务则我直接抛出异常。

举个栗子:

@Transational(propagation=Propagation.REQUIRED)
public void testMain(){
    A(a1);
    testB();
}
@Transational(propagation=Propagation.NEVER)
public void testB(){
    B(b1);
    B(b2);
}

​ 此种场景运行testMain会直接爆出异常,因为testB的事务传播机制是不允许调用它的事务存在事务的,此时testMain检测发现方法内部有异常就会发生回滚,所以数据库数据是没有发生更新的。

NESTED

​ 意思是:如果当前事务存在,那就在事务中进行嵌套执行,如果不存在,则开启一个事务,传播机制为REQUIRED。

  • 和REQUIRES_NEW的区别

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号