原创 一安 一安未来 2023-06-13 08:01 发表于北京
收录于合集#干货分享集141个
大家好,我是一安~
简介
事务的传播特性
假设我们有两个方法:methodA
和methodB
,如果methodA
调用methodB
,那么将采用什么样的事务形式,这就叫做事务的传播特性,传播特性是作用于内层方法。
目前,Spring
在TransactionDefinition
类中定义了以下7种传播特性,具体特性我们接下来会分析:
/**
* PROPAGATION_REQUIRED:如果不存在外层事务,就主动创建事务;否则使用外层事务
* PROPAGATION_REQUIRES_NEW:总是主动开启事务;如果存在外层事务,就将外层事务挂起
* PROPAGATION_NESTED:如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务
* PROPAGATION_SUPPORTS:如果不存在外层事务,就不开启事务;否则使用外层事务
* PROPAGATION_MANDATORY:如果不存在外层事务,就抛出异常;否则使用外层事务
* PROPAGATION_NOT_SUPPORTED:总是不开启事务;如果存在外层事务,就将外层事务挂起
* PROPAGATION_NEVER:总是不开启事务;如果存在外层事务,则抛出异常
*/
案例
介绍比较常用的PROPAGATION_REQUIRED
和PROPAGATION_REQUIRES_NEW
,其他的了解即可。
新建测试表
CREATE TABLE `demo1` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `demo2` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`addr` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
为了方便直接引入了
lombok
和mybatis-plus
,小编这里利用之前搭建SpringCloud Alibaba
的代码生成器直接生成了代码
增加配置
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springcloud?serverTimezone=GMT%2B8&useSSL=false&characterEncoding=utf-8&nullCatalogMeansCurrent=true
username: root
password: root
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 查看日志
mapper-locations: classpath:mapper/*.xml
测试
@SpringBootTest
public class TransactionTest {
/**
* PROPAGATION_REQUIRED:如果不存在外层事务,就主动创建事务;否则使用外层事务
* PROPAGATION_REQUIRES_NEW:总是主动开启事务;如果存在外层事务,就将外层事务挂起
* PROPAGATION_NESTED:如果不存在外层事务,就主动创建事务;否则创建嵌套的子事务
* PROPAGATION_SUPPORTS:如果不存在外层事务,就不开启事务;否则使用外层事务
* PROPAGATION_MANDATORY:如果不存在外层事务,就抛出异常;否则使用外层事务
* PROPAGATION_NOT_SUPPORTED:总是不开启事务;如果存在外层事务,就将外层事务挂起
* PROPAGATION_NEVER:总是不开启事务;如果存在外层事务,则抛出异常
* */
@Autowired
private Demo1Service demo1Service;
/**
* 测试:
* 1.demo1Service正常写入,不开启事务
* 2.demo2Service 制造int a = 1/0;异常,不开启事务
*
* 结论:
* 1.系统抛出java.lang.ArithmeticException: / by zero
* 2.demo1 和 demo2 正常写入数据,没有回滚
*/
@Test
public void noTransaction() {
Demo1 demo1 = new Demo1();
demo1.setName("一安未来").setAge("20");
demo1Service.insertDemo1(demo1);
}
/**
* 测试:
* 1.demo1Service正常写入,开启默认事务REQUIRED
* 2.demo2Service 制造int a = 1/0;异常,不开启事务
* 或
* 1.demo1Service正制造int a = 1/0;异常,开启默认事务REQUIRED
* 2.demo2Service 正常写入,不开启事务
*
* 结论:
* 1.系统抛出java.lang.ArithmeticException: / by zero
* 2.demo1 和 demo2 执行回滚
* 3.如果存在外层事务,则使用外层事务;
*/
@Test
public void transaction_REQUIRED() {
Demo1 demo1 = new Demo1();
demo1.setName("一安未来").setAge("20");
demo1Service.insertDemo1(demo1);
}
/**
* 测试:
* 1.demo1Service正常写入,不开启事务
* 2.demo2Service 制造int a = 1/0;异常,开启默认事务REQUIRED
*
* 结论:
* 1.系统抛出java.lang.ArithmeticException: / by zero
* 2.demo1 未回滚,demo2 执行回滚
* 3.如果不存在外层事务,就主动开启事务;
*/
@Test
public void transaction_REQUIRED2() {
Demo1 demo1 = new Demo1();
demo1.setName("一安未来").setAge("20");
demo1Service.insertDemo1(demo1);
}
/**
* 测试:
* 1.demo1Service制造int a = 1/0;异常,开启默认事务REQUIRED
* 2.demo2Service 正常写入,开启事务REQUIRES_NEW
*
* 结论:
* 1.系统抛出java.lang.ArithmeticException: / by zero
* 2.demo1执行回滚 , demo2未回滚
* 3.总是主动开启事务;如果存在外层事务,就将外层事务挂起
*/
@Test
public void transaction_REQUIRED3() {
Demo1 demo1 = new Demo1();
demo1.setName("一安未来").setAge("20");
demo1Service.insertDemo1(demo1);
}
}
注意事项:
禁止放在
非public
方法上不注意事务上下文环境,即事务的传播行为
@Transactional
注解属性rollbackFor
设置错误同一个类中方法调用,导致
@Transactional
失效异常
catch
捕捉导致@Transactional
失效数据库引擎不支持事务
补充说明
Spring的事务是如何进行回滚的?
在使用Spring
框架的时候,可以有两种事务的实现方式,一种是编程式事务,有用户自己通过代码来控制事务的处理逻辑,还有一种是声明式事务,通过@Transactional
注解来实现。
其实事务的操作本来应该是由数据库来进行控制,但是为了方便用户进行业务逻辑的操作,Spring
对事务功能进行了扩展实现,一般我们很少会用编程式事务,更多的是通过添加@Transactional
注解来进行实现,当添加此注解之后事务的自动功能就会关闭,由Spring
框架来帮助进行控制。
其实事务操作是AOP
的一个核心体现,正常情况下要通过通知来完成核心功能,但事务不是通过通知来实现,而是通过TransactionInterceptor
中invoke
来实现的
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;
return this.invokeWithinTransaction(invocation.getMethod(), targetClass, new TransactionAspectSupport.CoroutinesInvocationCallback() {
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
public Object getTarget() {
return invocation.getThis();
}
public Object[] getArguments() {
return invocation.getArguments();
}
});
}
当一个方法添加@Transactional
注解之后,Spring
会基于这个类生成一个代理对象,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务的自动提交给关闭,然后去执行具体的业务逻辑。
如果执行逻辑没有出现异常,那么代理逻辑就会直接提交:TransactionAspectSupport
中commitTransactionAfterReturning()->commit()->processCommit()->doCommit()
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject)status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
this.logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
} catch (SQLException var5) {
throw this.translateException("JDBC commit", var5);
}
}
如果出现任何异常情况,那么直接进行回滚操作:TransactionAspectSupport
中completeTransactionAfterThrowing()->rollback()->processRollback()->doRollback()
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject)status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
this.logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
} catch (SQLException var5) {
throw this.translateException("JDBC rollback", var5);
}
}
当事务执行完毕需要清除相关事务:TransactionAspectSupport
中cleanupTransactionInfo()
如果这篇文章对你有所帮助,或者有所启发的话,帮忙 分享、收藏、点赞、在看,你的支持就是我坚持下去的最大动力!
面试官:说说什么是本地缓存、分布式缓存以及多级缓存,它们各自的优缺点?
SpringBoot整合Prometheus+Pushgetway采集自定义指标数据(值得收藏)