1.事务的简介
什么是事务
事务是指是程序中一系列严密的逻辑操作,而且所有操作必须全部成功完成,否则在每个操作中所作的所有更改都会被撤消。通俗理解为:多件事当成一件事一起干,好比大家同在一条船,要不一起活,要不一起死
事务的四个基本要素(ACID)
-
原子性(Atomicity)
操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
-
一致性(Consistency)
事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
或者说 事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
-
隔离性(Isolation)
隔离性指当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。 即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行。
同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
-
持久性(Durability)
当事务正确完成后,对于数据的改变是永久性的。也就是说事务对数据库的所有更新将被保存到数据库,不能回滚。
2.Spring transaction 介绍
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
2.1 事务注解简介
查看@Transactional 注解我们可以发现,spring的事务包含以下几个方面
事务管理
Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器。
事务属性
隔离级别 isolation
传播行为 propagation
超时 timeout
是否只读 readOnly
回滚规则 rollbackFor
2.2 spring事务管理和数据库事务关系
事务这个概念,怎么实现的都是数据库层面的。spring的事务管理,方便了你写代码,只是把事务管理的代码交给了spring,完事的时候,也是由spring把事务的相关命令提交到数据库的。
3.事务管理 transactionManager
Spring事务管理涉及的接口的联系如下:
事务接口关系.jpg)
如上图,Spring事务管理高层抽象主要有3个:
PlatformTransactionManager :事务管理器(用来管理事务,包含事务的提交,回滚)
TransactionDefinition :事务定义信息(隔离,传播,超时,只读)
TransactionStatus :事务具体运行状态
3.1事务管理器PlatformTransactionManager
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
3.2 事务定义信息TransactionDefinition
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
int TIMEOUT_DEFAULT = -1;
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
@Nullable
String getName();
}
3.3 事务具体运行状态TransactionStatus
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
@Override
void flush();
boolean isCompleted();
}
Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。
下面分别介绍各个平台框架实现事务管理的机制。
JDBC事务
如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:
实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。
Hibernate事务
如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的``声明:
sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。
Java持久化API事务(JPA)
Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:
JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。
Java原生API事务(JTA)
JTA,即Java Transaction API,JTA允许应用程序执行分布式事务处理。
如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:
JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。
4.事务属性
事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面
下面详细介绍一下各个事务属性。
4.1隔离级别(Isolation)
4.1.1 隔离级别种类
查看Isolation 源码可知,spring 隔离级别定义以下几种
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),//-1
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),//1
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),//2
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),//4
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);//8
}
DEFAULT (默认隔离级别)
使用数据库设置的隔离级别 (默认隔离级别)
MySQL默认采用的REPEATABLE_READ隔离级别,Oracle默认采用的READ_COMMITTED隔离级别。
READ_UNCOMMITTED (读未提交)
允许读取尚未提交的的数据变更 ( 隔离级别最低 ,并发性能高)
可能会出现脏读、幻读、不可重复读。
READ_COMMITTED(读已提交)
允许读取并发事务已经提交的数据(锁定正在读取的行)
可以阻止脏读,但是幻读或不可重复读仍有可能发生
REPEATABLE_READ(可重复读)
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改(锁定所读取的所有行)
可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE (串行)
串行操作,保证所有的情况不会发生(锁表,性能最差)
最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就说,该级别可以阻止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。在事务的并发操作中可能会出现脏读,不可重复读,幻读。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ_UNCOMMITTED | Y | Y | Y |
READ_COMMITTED | N | Y | Y |
REPEATABLE_READ | N | N | Y |
SERIALIZABLE | N | N | N |
4.1.2 并发事务导致的问题
脏读(无效数据读出)
一个事务读取另外一个事务还没有提交的数据叫脏读。
例如:事务T1修改了某个表中的一行数据,但是还没有提交,这时候事务T2读取了被事务T1修改后的数据,之后事务T1因为某种原因回滚(Rollback)了,那么事务T2读取的数据就是脏的(无效的)。
解决方案: 1、只能读取已事务提交的数据 READ_COMMITTED
2、只要我读的时候,别人不能读,所以锁定当前行就可以了。REPEATABLE_READ
重点是:一次事务读出的数据是无效
不可重复读(多次读出不同的数据)
不可重复读是指在同一个事务内,两次相同的查询返回了不同的结果。
例如:事务T1会读取两次数据,在第一次读取某一条数据后,事务T2修改了该数据并提交了事务,T1此时再次读取该数据,两次读取便得到了不同的结果
解决方案:只要我读的时候,别人不能读,所以锁定当前行就可以了。REPEATABLE_READ
重点是:一个事务多次读取的数据内容不一致
幻读(多次操作记录数不同)
系统事务A将数据库中所有数据都删除的时候,但是事务B就在这个时候新插入了一条记录,当事务A删除结束后发现还有一条数据,就好像发生了幻觉一样。这就叫幻读。
解决办法:把数据库的事务隔离级别调整到SERIALIZABLE_READ(序列化执行),或者数据库使用者自己进行加锁来保证。
重点是:一个事务多次操作的数据个数不一致
- 题外话:幻读的重点在于新增或者删除 (数据条数变化)。同样的条件, 第1次和第2次读出来的记录数不一样
4.2传播行为(Propagation behavior)
4.2.1传播行为定义
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。 是重新创建事务还是使用父方法的事务?父方法的回滚对子方法的事务是否有影响?这些都是 可以通过事务传播机制来决定的。
例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
4.2.2 传播的类型
查看 Propagation类,可以发现spring的事务传播有下面7种
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
}
事务传播类型
事务传播行为 | 说明 |
---|---|
REQUIRED | 支持当前事务,假设当前没有事务。就新建一个事务 |
SUPPORTS | 支持当前事务,假设当前没有事务,就以非事务方式运行 |
MANDATORY | 支持当前事务,假设当前没有事务,就抛出异常 |
REQUIRES_NEW | 新建事务,假设当前存在事务。把当前事务挂起 |
NOT_SUPPORTED | 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 |
NEVER | 以非事务方式运行,假设当前存在事务,则抛出异常 |
NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |
REQUIRED(有就用,没有就新建)
默认的行为。支持当前事务,假设当前没有事务。就新建一个事务
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
serviceC.methodB();
// do something
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// do something
}
- 单独调用methodB方法时,因为当前上下文不存在事务,所以会开启一个新的事务。
- 调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。
SUPPORTS(有就用,没有就不用)
支持当前事务,假设当前没有事务,就以非事务方式运行
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
serviceC.methodB();
// do something
}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
// do something
}
- 单独调用methodB方法时,因为当前上下文不存在事务,就以非事务方式运行。
- 调用methodA方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB时,methodB发现当前上下文有事务,因此就加入到当前事务中来。
MANDATORY(有就用,没有就报错)
支持当前事务,假设当前没有事务,就抛出异常
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
serviceC.methodB();
// do something
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// do something
}
- 当单独调用methodB时,因为当前没有一个活动的事务,则会抛出异常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);
- 当调用methodA时,methodB则加入到methodA的事务中,事务地执行。
REQUIRES_NEW(有也不用,独立的)
新建事务,假设当前存在事务。把当前事务挂起
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
serviceC.methodB();
doSomeThingB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// do something
}
- 当单独调用methodB时,会开启一个新的事务。
- 当调用methodA时,相当于调用
main() {
TransactionManager tm = null;
try {
//获得一个JTA事务管理器
tm = getTransactionManager();
tm.begin();//开启一个新的事务
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();//挂起当前事务
try {
tm.begin();//重新开启第二个事务
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();//提交第二个事务
} Catch(RunTimeException ex) {
ts2.rollback();//回滚第二个事务
} finally{
//释放资源
}
//methodB执行完后,恢复第一个事务
tm.resume(ts1);
doSomeThingB();
ts1.commit();//提交第一个事务
} catch (RunTimeException ex) {
ts1.rollback();//回滚第一个事务
} finally {
//释放资源
}
}
在这里,我把ts1称为外层事务,ts2称为内层事务。从上面的代码可以看出,ts2与ts1是两个独立的事务,互不相干。Ts2是否成功并不依赖于 ts1。如果methodA方法在调用methodB方法后的doSomeThingB方法失败了,而methodB方法所做的结果依然被提交。而除了 methodB之外的其它代码导致的结果却被回滚了。
NOT_SUPPORTED(有也不用)
总是非事务地执行,并挂起任何存在的事务。使用JtaTransactionManager作为事务管理器。
NEVER(有就报错)
总是非事务地执行,如果存在一个活动事务,则抛出异常。
NESTED(嵌套)
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
这是一个嵌套事务,使用JDBC 3.0驱动时,仅仅支持DataSourceTransactionManager作为事务管理器。需要JDBC 驱动的java.sql.Savepoint类。使用PROPAGATION_NESTED,还需要把PlatformTransactionManager的nestedTransactionAllowed属性设为true(属性值默认为false)。
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
doSomeThingA();
serviceC.methodB();
doSomeThingB();
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
// do something
}
单独执行methodB方法时,按REQUIRED属性执行。因为当前上下文不存在事务,所以会开启一个新的事务。
-
当调用methodA时,相当于调用
main(){ Connection con = null; Savepoint savepoint = null; try{ con = getConnection(); con.setAutoCommit(false); doSomeThingA(); savepoint = con2.setSavepoint(); try{ methodB(); } catch(RuntimeException ex) { con.rollback(savepoint); } finally { //释放资源 } doSomeThingB(); con.commit(); } catch(RuntimeException ex) { con.rollback(); } finally { //释放资源 } }
当methodB方法调用之前,调用setSavepoint方法,保存当前的状态到savepoint。如果methodB方法调用失败,则恢复到之前保存的状态。但是需要注意的是,这时的事务并没有进行提交,如果后续的代码(doSomeThingB()方法)调用失败,则回滚包括methodB方法的所有操作。嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。
4.2.3 NESTED 与REQUIRES_NEW的区别
它们非常类似,都像一个嵌套事务,如果不存在一个活动的事务,都会开启一个新的事务。
使用 PROPAGATION_REQUIRES_NEW时,内层事务与外层事务就像两个独立的事务一样,一旦内层事务进行了提交后,外层事务不能对其进行回滚。两个事务互不影响。两个事务不是一个真正的嵌套事务。同时它需要JTA事务管理器的支持。
使用PROPAGATION_NESTED时,外层事务的回滚可以引起内层事务的回滚。而内层事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED时,需要JDBC 3.0以上驱动及1.4以上的JDK版本支持。其它的JTATrasactionManager实现可能有不同的支持方式。
PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back 而不依赖于外部事务, 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行。
另一方面, PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint. 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back。
注意:以上的方法,methodA,methodB在不同的类中。
readonly这个属性,是放在传播行为中的,一般书都这么归类,readonly并不能影响数据库隔离级别,只是配置之后,不允许在事务中对数据库进行修改操作,仅此而已。
5.事务传播用例
5.1.不嵌套调用
5.1.1 无事务
public void methodA1() {
//更新数据操作
updateOfficePhone("021-method1");
System.out.println(1 / 0);
}
-
结果
数据库正常更新数据服务抛出java.lang.ArithmeticException: / by zero异常
5.1.2 有事务
@Transactional
public void methodA2() {
//更新数据操作
updateOfficePhone("021-methodA2");
System.out.println(1 / 0);
}
-
结果
服务抛出java.lang.ArithmeticException: / by zero异常
事务生效:数据库数据回滚,未更新。
5.1.3 同时调用多个事务方法
- 调用方
@Controller
public class UserController {
@Autowired
private ServiceA serviceA;
/**
* 调用多个有事务的方法
*/
@RequestMapping("/3")
@ResponseBody
public void my3() {
serviceA.methodA3();
serviceA.methodA2();
}
}
- 接口
@Service
public class ServiceA {
@Transactional
public void methodA2() {
updateOfficePhone("021-method2");
System.out.println(1 / 0);
}
@Transactional
public void methodA3() {
updateOfficePhone("021-method3");
}
}
- 结果
服务抛出java.lang.ArithmeticException: / by zero异常
methodA3正常执行;method2抛错,事务回滚
上面都是最基本的用法,不存在嵌套,事务之间彼此之前无影响。传播的行为的默认为Propagation.REQUIRED。
5.2 嵌套调用(针对事务方法在不同类)
5.2.1 REQUIRED 嵌套调用-都有事务
- 调用方
@Controller
public class UserController {
@Autowired
private ServiceA serviceA;
@RequestMapping("/Q1")
@ResponseBody
public void myQ1() {
serviceA.methodQ1();
}
@RequestMapping("/Q2")
@ResponseBody
public void myQ2() {
serviceA.methodQ2();
}
}
- 接口
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodQ1() {
updateOfficePhone("021-methodQ1");
serviceB.methodB1Error();
}
@Transactional
public void methodQ2() {
updateOfficePhone("021-methodQ1");
serviceB.methodB1();
System.out.println(1 / 0);
}
}
//内层
@Service
public class ServiceB {
//有报错
@Transactional
public void methodB1Error() {
updateMobile("138-methodB1Error");
System.out.println(1 / 0);
}
//无报错
@Transactional
public void methodB1() {
updateMobile("138-methodB1");
}
}
结果
服务抛出java.lang.ArithmeticException: / by zero异常
methodB1抛错,数据均未更新,事务都回滚-
分析
由于使用的是默认传播行为,调用methodQ1方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB1时,methodB1发现当前上下文有事务,因此就加入到当前事务中来。
对于上面方法任何一个位置报错,都会全部回滚
5.2.2 REQUIRED 嵌套调用-外部无事务
上面的方法改下:
public void methodQ3() {
updateOfficePhone("021-methodQ3");
serviceB.methodB1Error();
}
public void methodQ4() {
updateOfficePhone("021-methodQ4");
serviceB.methodB1();
System.out.println(1 / 0);
}
-
结果
methodQ3 :外部方法正常更新,内部回滚
methodQ4:正常更新,外部异常不影响内部事务
-
分析
由于外部无事务,内部事务完成后,不影响外部。外部异常也无法影响内部事务
5.2.3 REQUIRED 嵌套调用- 都有事务,外部try内部方法错误
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodQ5() {
updateOfficePhone("021-methodQ5");
try {
serviceB.methodB1Error();
}catch (RuntimeException e){
System.out.println(e.getMessage());
}
}
}
结果
服务抛出 java.lang.ArithmeticException: / by zero异常
服务抛出 Transaction rolled back because it has been marked as rollback-only
methodC1抛错,数据均未更新,事务都回滚-
分析
内层事务报错,会被标记rollback;外层事务正常执行,当外层事务准备提交事务时,发现内层被标记了,所以抛出Transaction rolled back because it has been marked as rollback-only。外层提交失败,所有操作都回滚
5.2.4 嵌套调用- 都有事务,外部try内部方法错误
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodQ6() {
updateOfficePhone("021-methodQ6");
serviceB.methodB1ErrorTry();
}
}
@Service
public class ServiceB {
@Transactional
public void methodB1ErrorTry() {
try{
updateMobile("138-methodB1ErrorTry");
System.out.println(1 / 0);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
-
结果
两个数据都正常更新
-
分析
因为内存事务错误被抓住,没有往外抛,相当于没有错误,所以事务正常执行
5.2.5 REQUIRES_NEW 嵌套调用-都有事务
把5.2.1的内部事务代码修改下:
@Service
public class ServiceA {
@Autowired
private ServiceB serviceB;
@Transactional
public void methodQ7() {
updateOfficePhone("021-methodQ7");
serviceB.methodB1NewError();
}
@Transactional
public void methodQ8() {
updateOfficePhone("021-methodQ8");
serviceB.methodB1New();
System.out.println(1 / 0);
}
}
@Service
public class ServiceB {
//有报错
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB1NewError() {
updateMobile("138-methodB1Error");
System.out.println(1 / 0);
}
//无报错
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB1New() {
updateMobile("138-methodB1");
}
}
-
结果
Q7:服务抛出java.lang.ArithmeticException: / by zero异常
methodB1Error抛错,数据均未更新,事务都回滚Q8:服务抛出java.lang.ArithmeticException: / by zero异常
methodQ8 回滚,methodB1New正常提交
-
分析
调用methodQ7方法时,因为当前上下文不存在事务,所以会开启一个新的事务。当执行到methodB1NewError时,由于使用的是REQUIRES_NEW传播行为,methodB1NewError又创建一个事务。由于报错事务回滚,错误抛出到外层。外层发现有错误,也进行事务回滚
调用methodQ8方法时,当执行到methodB1New时,由于使用的是REQUIRES_NEW传播行为,methodB1New又新建一个事务,执行结束后提交。外层事务接着执行,外层有报错,外层回滚。
从上面用例可以看出
1、如果不同的事务,大家互相无关联,各自提交。
2、事务中出现报错且抛出,事务会回滚。