spring事务传播机制
1、7种传播机制
我们在@Transactional注解的源码中可以看到,事务默认的机制是REQUIRED。进入Propagation中可以看到,spring定义了7种传播机制。如下所示,我这里贴上了网上的参考解释,不一定全面,大家也可以自己百度一下。这里可以先浏览一下,后续案例中进行验证,大家可以对照验证结果来看这里的解释,会更容易理解。重点理解REQUIRED、REQUIRES_NEW、NESTED这三种机制,也是工作中最常用的三种机制。
/**
* The transaction propagation type.
* <p>Defaults to {@link Propagation#REQUIRED}.
* @see org.springframework.transaction.interceptor.TransactionAttribute#getPropagationBehavior()
*/
Propagation propagation() default Propagation.REQUIRED;
public enum Propagation {
/**
* spring默认传播行为,支持当前事务,如果当前没有事务,则新建一个事务。
* 外层方法、内层方法不管哪个抛出异常,都会回滚,即使在外层方法中捕获内层方法抛出的异常也会回滚
*/
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
/**
* 支持当前事务(和REQUIRED行为一样),如果当前没有事务,就以非事务方式运行
*/
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
/**
* 支持当前事务(和REQUIRED行为一样),如果当前没有事务,则抛出异常,属于强制事务
*/
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
/**
* 新建事务,如果当前存在事务,则事务挂起,新增一个事务,新建的事务和当前事务没有任何关系,是两个独立的事务。
* 外层的方法失败回滚后,不能回滚内层方法的运行结果,内层方法失败抛出异常,外层方法如果进行捕获,也能不回滚外层方法运行结果。
*/
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
/**
* 不执行当前事务,而总是以非事务的方式执行
*/
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
/**
* 以非事务的方式执行,如果当前存在事务,则抛出异常,强制非事务
*/
NEVER(TransactionDefinition.PROPAGATION_NEVER),
/**
* 如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则进行与REQUIRED类似的操作
* 内层事务受外层事务的影响,外层事务不受内层事务的影响(前提 外层事务捕获了内层事务的异常)
*/
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) {this.value = value;}
public int value() { return this.value;}
}
2、案例准备
1、准备2张表,tablea、tableb
CREATE TABLE `tablea` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1;
CREATE TABLE `tableb` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1;
2、新建一个springboot项目,在pom文件中引入以下jar包,在yml文件中添加数据库和mybatis配置,需要修改为你的数据库账号、密码、库名、实体所在的包名
<properties>
<java.version>1.8</java.version>
<spring.boot.version>2.5.1</spring.boot.version>
<junit.versin>4.13</junit.versin>
<lombok.version>1.18.8</lombok.version>
<mysql.version>8.0.15</mysql.version>
<mybatis.version>2.0.1</mybatis.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
</dependencies>
server:
port: 8081
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
mybatis:
type-aliases-package: org.west.sky.scripture.transaction.model
configuration:
map-underscore-to-camel-case: true
3、准备业务代码和单元测试的代码
//mapper
@Mapper
public interface ITableMapper {
@Insert("INSERT INTO tablea(id, name) VALUES(#{id}, #{name})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insertTableA(TableEntity tableEntity);
@Insert("INSERT INTO tableb(id, name) VALUES(#{id}, #{name})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insertTableB(TableEntity tableEntity);
}
//entity
@Data
public class TableEntity {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
}
//service
public interface ITableService {
void insertTableA(TableEntity tableEntity);
void insertTableB(TableEntity tableEntity);
}
//serviceimpl
@Service
public class TableServiceImpl implements ITableService {
@Autowired
ITableMapper tableMapper;
@Override
public void insertTableA(TableEntity tableEntity) {tableMapper.insertTableA(tableEntity);}
@Override
public void insertTableB(TableEntity tableEntity) {tableMapper.insertTableB(tableEntity);}
}
//controller
@RestController
@RequestMapping("/transaction")
public class TransactionTestController {
@Autowired
private TransactionServiceA transactionServiceA;
@RequestMapping("/test")
public String testTransaction() {
transactionServiceA.methodA();
return "SUCCESS";
}
}
//junit
@SpringBootTest
@AutoConfigureMockMvc
class TransactionTestControllerTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testTransaction() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/transaction/test")).andExpect(MockMvcResultMatchers.status().isOk());
}
}
4、事务操作代码和spring工具类
@Service
public class TransactionServiceA {
@Autowired
private ITableService tableService;
@Autowired
private TransactionServiceB transactionServiceB;
/**
* 这里我们为了方便后续的调试,定义了一个methodA,就不需要每次都去修改controller中调用的方法了
*/
public void methodA() {
//可以先忽略这里的调用方法,后续会解释
TransactionServiceA bean = SpringUtil.getBean(TransactionServiceA.class);
bean.methodA__4();
}
public void methodA_1() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_1();
}
}
@Service
public class TransactionServiceB {
@Autowired
private ITableService tableService;
public void methodB_1() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
}
5、运行junit中的测试方法,看看数据库中能不能正常插入数据。如果可以插入,进行下一步,没有插入,可以检查控制台有没有报错,根据报错解决问题后进行下一步操作。
3、测试案例
这里我就不展示数据库的执行结果了,直接说结果和原因,建议大家自己是执行代码验证一下结果。每次执行案例时,记得修改methodA中调用的方法。
具体代码详见
spring事务的7中传播机制
注意:后续的测试中,如果大家不清楚是没有执行插入操作,还是执行了插入操作,但是由于报错进行了回滚。可以在每个案例执行前,先执行TRUNCATE命令,把数据库还原,不了解TRUNCATE原理的可以百度一下。因为我们设置了表主键自增,所以正常插入操作,表的主键应该是1、2、3…这样递增。如果你发现执行了案例后,表中没有数据,可以收到插入1条数据,如果主键自动生成的是2,而不是1,表示进行了数据回滚操作,但是如果是1,表示根本没有执行插入操作。通过控制台中是否打印了“methodA”、“methodB”也可以判断方法有没有执行。
TRUNCATE tablea;
TRUNCATE tableb;
1、有事务的方法调用无事务的方法
//加入到TransactionServiceA类中
/**
* 条件: A有事务、B没有事务、B报错
* 结果:A、B均未插入数据(都回滚)
* 原因:A、B在同一个事务中,可以理解为A、B在一个方法中
*/
@Transactional
public void methodA_2_1() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_1();
}
/**
* 条件: A有事务、B没有事务、A报错
* 结果:A、B均未插入数据(都回滚)
* 原因:A、B在同一个事务中,可以理解为A、B在一个方法中
*/
@Transactional
public void methodA_2_2() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_2();
throw new RuntimeException();
}
/**
* 条件: A有事务、B没有事务、捕获B抛出的异常
* 结果:A、B均插入数据(都未回滚)
* 原因:A、B在同一个事务中,捕获异常后,并没抛出异常,可以理解为A、B在一个方法中,B这段代码加了异常捕获
*/
@Transactional
public void methodA_2_3() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB_1();
} catch (Exception e) {
e.printStackTrace();
}
}
//加入到TransactionServiceB类中
public void methodB_2() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
2、没有事务的方法调用了有事务的方法
//加入到TransactionServiceA类中
/**
* 条件: A没有事务、B有事务、B抛出异常
* 结果:A插入数据,B未插入数据(A未回滚,B回滚)
* 原因:A以无事务方式运行,B以有事务方式运行,A、B独立,互不影响
*/
public void methodA_3_1() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_3();
}
/**
* 条件: A没有事务、B有事务、捕获B抛出的异常
* 结果:A插入数据,B未插入数据(A未回滚,B回滚)
* 原因:A以无事务方式运行,B以有事务方式运行,A、B独立,互不影响
*/
public void methodA_3_2() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB_3();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 条件: A没有事务、B有事务、A抛出异常
* 结果:A、B均插入数据(都未回滚)
* 原因:A以无事务方式运行,B以有事务方式运行,A报错不影响B的事务,A、B独立,互不影响
*/
public void methodA_3_3() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_4();
throw new RuntimeException();
}
//加入到TransactionServiceB类中
@Transactional
public void methodB_3() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
@Transactional
public void methodB_4() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
3、内层方法以REQUIRED执行事务
//加入到TransactionServiceA类中
/**
* 条件: A有事务、B有事务(REQUIRED)、B抛出异常
* 结果:A、B均未插入数据(都回滚)
* 原因:B加入A的事务中,A、B在同一个事务中
*/
@Transactional
public void methodA_4_1() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_5();
}
/**
* 条件: A有事务、B有事务(REQUIRED)、A抛出异常
* 结果:A、B均未插入数据(都回滚)
* 原因:B加入A的事务中,A、B在同一个事务中
*/
@Transactional
public void methodA_4_2() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_6();
throw new RuntimeException();
}
/**
* 条件: A有事务、B有事务(REQUIRED)、捕获B抛出的异常
* 结果:A、B均未插入数据(都回滚)
* 原因:B加入A的事务中,A、B在同一个事务中。A、B互相影响,有一个报错就全部回滚,即是在外层方法捕获异常,也会回滚。
* 区别于{@link #methodA_2_3}
*/
@Transactional
public void methodA_4_3() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB_5();
} catch (Exception e) {
e.printStackTrace();
}
}
//加入到TransactionServiceB类中
@Transactional(propagation = Propagation.REQUIRED)
public void methodB_5() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB_6() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
4、内层方法以REQUIRED_NEW执行事务
//加入到TransactionServiceA类中
/**
* 条件: A有事务、B有事务(REQUIRED_NEW)、B抛出异常
* 结果:A、B均未插入数据(都回滚)
* 原因:A事务挂起,B新增一个事务,B报错,B回滚,A未进行捕获,导致A也产生报错,A也回滚
*/
@Transactional
public void methodA_5_1() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_7();
}
/**
* 条件: A有事务、B有事务(REQUIRED_NEW)、A抛出异常
* 结果:A未插入数据,B插入数据(A回滚,B不回滚)
* 原因:A事务挂起,B新增一个事务,A报错,但是此时B的事务已经提交,B不回滚,A回滚
*/
@Transactional
public void methodA_5_2() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_8();
throw new RuntimeException();
}
/**
* 条件: A有事务、B有事务(REQUIRED_NEW)、捕获B抛出的异常
* 结果:A插入数据,B未插入数据(A未回滚,B回滚)
* 原因:A事务挂起,B新增一个事务,B报错,B回滚,但A捕获的B抛出的异常,所以A不回回滚
* 区别于{@link #methodA_4_3}
*/
@Transactional
public void methodA_5_3() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB_7();
} catch (Exception e) {
e.printStackTrace();
}
}
//加入到TransactionServiceB类中
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB_7() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB_8() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
5、内层方法以SUPPORTS执行事务
//加入到TransactionServiceA类中
/**
* 条件: A有事务、B有事务(SUPPORTS)、A抛出异常
* 结果:A、B均未插入数据(都回滚)
* 原因:A有事务,B加入A的事务,相当于(REQUIRED)
* 等于{@link #methodA_4_2}
*/
@Transactional
public void methodA_6_2() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_10();
throw new RuntimeException();
}
/**
* 条件: A有事务、B有事务(SUPPORTS)、捕获B抛出的异常
* 结果:A、B均未插入数据(都回滚)
* 原因:A有事务,B加入A的事务,相当于(REQUIRED)
* 等于{@link #methodA_4_3}
*/
@Transactional
public void methodA_6_3() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB_9();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 条件: A没有事务、B有事务(SUPPORTS)、B抛出异常
* 结果:A、B均插入数据(都不回滚)
* 原因:A无事务,B也以非事务方式执行。相当于A、B都没有事务
* 等于{@link #methodA_1}
*/
public void methodA_6_4() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_9();
}
//加入到TransactionServiceB类中
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB_9() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB_10() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
6、内层方法以NOT_SUPPORTS执行事务
//加入到TransactionServiceA类中
/**
* 条件: A有事务、B有事务(NOT_SUPPORTS)、B抛出异常
* 结果:A未插入数据、B插入数据(A回滚、B不回滚)
* 原因:A有事务,B总是以非事务的方式执行
* 区别于{@link #methodA_2_1}
*/
@Transactional
public void methodA_7_1() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_11();
}
/**
* 条件: A有事务、B有事务(NOT_SUPPORTS)、A抛出异常
* 结果:A未插入数据、B插入数据(A回滚、B不回滚)
* 原因:A有事务,B总是以非事务的方式执行
* 区别于{@link #methodA_2_2}
*/
@Transactional
public void methodA_7_2() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_12();
throw new RuntimeException();
}
/**
* 条件: A有事务、B有事务(NOT_SUPPORTS)、捕获B抛出的异常
* 结果:A、B均插入数据(都不回滚)
* 原因:A有事务,B总是以非事务的方式执行,但是B抛出的异常被捕获,A不会进行回滚操作
*/
@Transactional
public void methodA_7_3() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB_11();
} catch (Exception e) {
e.printStackTrace();
}
}
//加入到TransactionServiceB类中
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB_11() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB_12() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
7、内层方法以MANDATORY执行事务
//加入到TransactionServiceA类中
/**
* 条件: A有事务、B有事务(MANDATORY)、B抛出异常
* 结果:A、B均未插入数据(都回滚)
* 原因:A有事务,B支持A的事务,和REQUIRED一样
* 等于{@link #methodA_4_1}
*/
@Transactional
public void methodA_8_1() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_13();
}
/**
* 条件: A有事务、B有事务(MANDATORY)、A抛出异常
* 结果:A、B均未插入数据(都回滚)
* 原因:A有事务,B支持A的事务,和REQUIRED一样
* 等于{@link #methodA_4_2}
*/
@Transactional
public void methodA_8_2() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_13();
}
/**
* 条件: A有事务、B有事务(MANDATORY)、捕获B抛出的异常
* 结果:A、B均未插入数据(都回滚)
* 原因:A有事务,B支持A的事务,和REQUIRED一样
* 等于{@link #methodA_4_3}
*/
@Transactional
public void methodA_8_3() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB_13();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 条件: A没有事务、B有事务(MANDATORY)
* 结果:A插入数据、B未插入数据(A未回滚,B未执行插入操作)
* 原因:MANDATORY如果当前无事务,会报错,因为A先执行且无事物,所以A正常插入。
* 在进入B方法前就报错 No existing transaction found for transaction marked with propagation 'mandatory'
* 所以B方法未执行
*/
public void methodA_8_4() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_14();
}
//加入到TransactionServiceB类中
@Transactional(propagation = Propagation.MANDATORY)
public void methodB_13() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB_14() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
8、内层方法以NEVER执行事务
//加入到TransactionServiceA类中
/**
* 条件: A有事务、B有事务(NEVER)
* 结果:A、B均未插入数据(A回滚,B未执行插入操作)
* 原因:A有事务,B判断当前有事务,会报错,所以A会回滚
* 在进入B方法前就报错 existing transaction found for transaction marked with propagation 'never'
* 所以B方法未执行
*/
@Transactional
public void methodA_9_1() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_16();
}
/**
* 条件: A没有事务、B有事务(NEVER),B抛出异常
* 结果:A、B均插入数据(都不回滚)
* 原因:A无有事务,B以非事务的方式运行,A、B都没有事务,所以都不会回滚
*/
public void methodA_9_2() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_15();
}
//加入到TransactionServiceB类中
@Transactional(propagation = Propagation.NEVER)
public void methodB_15() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
@Transactional(propagation = Propagation.NEVER)
public void methodB_16() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
9、内层方法以NESTED执行事务
//加入到TransactionServiceA类中
/**
* 条件: A有事务、B有事务(NESTED),B抛出异常
* 结果:A、B均未插入数据(都回滚)
* 原因:A有事务,B嵌套在A事务内,当B抛出异常时,B进行正常回滚操作。由于A并未捕获B的异常,所以A也会回滚
*/
@Transactional
public void methodA_10_1() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_17();
}
/**
* 条件: A有事务、B有事务(NESTED),A抛出异常
* 结果:A、B均未插入数据(都回滚)
* 原因:A有事务,B嵌套在A事务内,B事务受A事务的影响,即外层事务可以音响内层事务
*/
@Transactional
public void methodA_10_2() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_18();
throw new RuntimeException();
}
/**
* 条件: A有事务、B有事务(NESTED),捕获B抛出的异常
* 结果:A、B均未插入数据(都回滚)
* 原因:A有事务,B嵌套在A事务内,B抛出的异常被A捕获后,A的事务不受影响
* 区别于{@link #methodA_4_3}
*/
@Transactional
public void methodA_10_3() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
try {
transactionServiceB.methodB_17();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 条件: A没有事务、B有事务(NESTED),B抛出异常
* 结果:A插入数据、B未插入数据(A未回滚、B回滚)
* 原因:A无事务不回滚,B会像REQUIRED方式一样新建一个事务,当B抛出异常时,B会进行正常回滚。
*/
public void methodA_10_4() {
System.out.println("methodA");
tableService.insertTableA(new TableEntity());
transactionServiceB.methodB_17();
}
//加入到TransactionServiceB类中
@Transactional(propagation = Propagation.NESTED)
public void methodB_17() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
throw new RuntimeException();
}
@Transactional(propagation = Propagation.NESTED)
public void methodB_18() {
System.out.println("methodB");
tableService.insertTableB(new TableEntity());
}
4、事务不生效的几种特殊情况
1、MySQL存储引擎必须是InnoDB,Myisam引擎不支持事务。
2、事务注解@Transactional必须放在public方法上才能生效。
3、如果methodA上没有@Transactional注解,但是调用了一个本类的有@Transactional注解的methodB,事务也不会生效,这时候需要借助我们上文提供的SpringUtil从spring上下文中获取实例才可以,因为注解的本质是aop和动态代理。
4、@Transactional 注解的方法所在的类必须被 Spring 管理。这个很好理解,因为我们所使用的声明式注解,本身就是spring,如果不被spring容易托管,是肯定不会生效的。