1、说明
1、Spring中开启事务的方式主要有两种:
编程式事务和声明式事务
。2、事务是我们开发过程中经常会使用到的,为了在业务执行过程中出现异常时,回滚到原始状态。而事务的回滚在大多数情况下都是靠着 exception(异常)来触发回滚的,当事务机制捕捉到异常,就会开始回滚。
3、但往往也会出现情况:在业务代码中,需要对异常单独进行处理,异常不会抛出,但需要事务回滚的情况,这个时候就需要手动调用回滚
2、声明式性事务@Transacational
@Transactional 声明式事务,是开发过程中最常用的开启事务的方式。
也可以通过切面,对整个业务层的方法进行事务控制
优点:
使用方便,而且对代码的侵入性低
这种方式,默认是通过 捕获 RunTimeException 来触发事务回滚,只要是 RuntimeException 以及 它的子类,都可以触发事务回滚。也可以修改触发事务回滚的异常范围,可以通过修改 @Transactional 中的 rollbackFor 属性,修改异常范围。比如:
@Transactional(rollbackFor = Exception.class),修改完成后,只要是 Exception 类的子类,都可以触发事务回滚
3、@Transactional不适用场景
有时候需要对异常进行特殊处理,异常被捕获无法抛出时,声明式事务就失效不可用。
示例:抛出异常被捕获,无法触发事务回滚。
//业务代码
try{
//业务处理出现异常
} catch (Exception e) {
// 捕获异常,打印异常,或其他处理。但不抛出新的异常
e.printStackTrace();
//可以将捕获后的异常,封装为新的业务异常抛出(正常回滚)
throw new xxxxException(e.getMessage);
}
//此时return语句能够执行
return xxx;
此处,可以将捕获后的异常,封装为新的业务异常抛出
4、@Transactional注解事务失效的几种场景及原因
在开发过程中,我们需要同时进行多个对数据的操作,这时候需要使用事务去控制多个操作的一致性。
通过声明式事务**@Transactional** 注解修饰方法的形式开启事务,需要注意到以下这些情况会出现事务失效。
4.1、数据库引擎不支持事务
例如我们使用Mysql数据,在 5.5 版本之前,Mysql 默认的数据引擎都是 MyISAM 的,而
MyISAM
是不支持事务
的;在 5.5 版本之后,默认的数据引擎是InnoDB
的,它是支持事务
的。
Spring 对事务的管理实现又是基于数据库的,所以
当数据库引擎不支持事务的时候,自然就不会有起作用的事务机制
了,所有我们在选择数据库的时候,需要去选择支持事务的数据引擎。
4.2、添加事务的类没有被Spring管理
当添加了注解或者全局事务配置了路径,但是需要用到事务的方法所在的类没有注入到Spring容器中,这样事务也不会生效,通常我们都是添加到业务逻辑处理层,通常都是添加
@Service
,将当前类注入到Spring中。
4.3、@Transactional作用的方法不是public修饰的
@Transactional注解应用在非public修饰的方法上,Transactional将会失效。这是因为在Spring AOP代理时,事务拦截器只能拦截public方法,而非public方法将直接被执行,不会经过代理。因此,建议将@Transactional注解只应用在public方法上。
4.4、@Transactional的rollbackFor属性设置错误
rollbackFor是@Transactional注解的一个属性,用于指定能够触发事务回滚的异常类型。Spring默认只在遇到未检查unchecked异常(继承自RuntimeException的异常)或者Error时才回滚事务;其他异常不会触发回滚事务。如果想让其他异常也能触发回滚事务,可以在@Transactional注解中加上rollbackFor属性,设置Exception异常就回滚,这样不管是Exception还是RuntimeException,spring都能帮助我们去回滚数据了。
示例:当抛出Exception异常时,想要触发事务回滚,就要设置@Transactional(rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)
public void test() {
// 业务代码
if(1 == 1) {
throw new Exception("Exception 异常");
}
}
4.5、@Transactional的propagation属性设置错误
设置
@Transactional(propagation = xxxx)
可以配置 Spring 的事务传播机制的,当事务传播机制设置为不支持事务是,事务也是不会生效的。propagation参数指定了事务的传播行为,即在一个事务方法中调用另一个事务方法时,两个事务如何关联。
示例:当设置为 propagation = Propagation.NOT_SUPPORTED 时,表示不以事务运行,当前若存在事务则挂起。
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void test1() {
// 业务代码
}
propagation有以下几种取值:
- REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则与REQUIRED类似,创建一个新的事务。
propagation的默认值是REQUIRED。
4.6、调用同类的方法,事务失效
调用本类方法导致传播行为失效,同一个 Service 的两个方法之间调用,就会出现这个问题,原因还是在代理对象这里,我们期待的调用是一个代理类的调用,但是我们若是直接在方法中内部调用,不好意思,被调用的方法的事务失效,没有被 AOP 增强。
**示例:**方法addStudents调用本类中的方法addStudent
@Service
public class StudentService {
@Transactional
public void addStudent(Student student) {
// do something
}
public void addStudents(List<Student> students) {
for (Student student : students) {
// 这里直接调用同类的方法,事务不会生效
addStudent(student);
}
}
}
解决方案一:自己调用自己,自己注入自己
。你可能看见过这样的代码,就是为了解决这个问题的。这种解决方案最常见。
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES)
public void a (){
service.b();
}
方案二:还有一种方法可以在同类调用的方法上,通过AOP代理调用,即使用AopContext.currentProxy()
方法获取当前类的代理对象,然后通过代理对象调用目标方法,这样就可以执行事务切面,进行事务增强。
@Service
public class StudentService {
@Transactional
public void addStudent(Student student) {
// do something
}
public void addStudents(List<Student> students) {
for (Student student : students) {
// 这里通过代理对象调用同类的方法,事务会生效
((StudentService)AopContext.currentProxy()).addStudent(student);
}
}
}
方案三:在同类调用的方法上,使用@Async
注解,这样就可以在异步线程中执行目标方法,而不是在当前线程中执行,这样就可以避免事务失效的问题。
@Service
public class StudentService {
@Transactional
@Async
public void addStudent(Student student) {
// do something
}
public void addStudents(List<Student> students) {
for (Student student : students) {
// 这里在异步线程中调用同类的方法,事务会生效
addStudent(student);
}
}
}
4.7、异常被捕获,事务无法回滚
事务机制的回滚,是 通过异常来触发事务回滚的。在开发过程中,会出现异常被捕获处理了,而且没有再抛出新的异常,就会导致异常丢失,无法触发回滚。
**示例1:**不会触发回滚的情况
@Transactional
public void test1() {
// 演示直接抛出异常被捕获
try {
throw new RuntimeException();
} catch (RuntimeException e) {
// 不做处理,虽然有异常,但异常丢失,无法触发事务回滚
}
}
**示例2:**catch 捕获异常后,再抛出
@Transactional
public void test1() {
// test code
try {
throw new RuntimeException();
} catch (RuntimeException e) {
// 不做处理,虽然有异常,但异常丢失,无法触发事务回滚
throw e;
}
}
5、spring事务控制手动回滚
Spring事务控制手动回滚是指在某些情况下,我们需要主动触发事务的回滚,而不是依赖于Spring的默认回滚策略。Spring的默认回滚策略是对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。有两种常用的方法可以实现手动回滚:
- 手动抛出一个运行时异常,例如
throw new RuntimeException()
,这样Spring就会捕获到这个异常,并执行事务回滚。- 通过Spring提供的事务切面支持类
TransactionAspectSupport
,调用其currentTransactionStatus().setRollbackOnly()
方法,这样就可以标记当前事务为回滚状态,而不需要抛出异常。使用手动回滚的方法时,需要注意以下几点:
- 被回滚的方法必须使用
@Transactional
注解,否则无法开启事务管理,也就无法回滚。- 被回滚的方法不能在同一个类中直接调用,否则无法走代理类,也就无法回滚,需要通过AOP代理调用或者注入自身的bean来调用。
- 被回滚的方法不能使用
try catch
来捕获异常,否则无法触发回滚,需要在方法签名上声明throws
或者在catch
块中重新抛出异常。
**示例:**手动回滚事务文章来源:https://www.toymoban.com/news/detail-490643.html
//假设这是一个service类的片段
try{
//出现异常
} catch (Exception e) {
// 捕获异常,打印异常,或其他处理。但不抛出新的异常
e.printStackTrace();
// 手动回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
//此时return语句能够执行
return xxx;
6、AOP配置全局事务管理
示例:文章来源地址https://www.toymoban.com/news/detail-490643.html
package com.gd.gd_service.config;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* @Auther: hippoDocker
* @Date:2021年12月21日20:51:27
* @Description: TODO 全局事务配置
*/
@Configuration
@Aspect
public class TransactionManagerConfig {
private static final Logger logger = LoggerFactory.getLogger(TransactionManagerConfig.class);
/**
* 配置方法过期时间,默认-1,永不超时,单位秒
*/
@Value("${spring.application.transactiontimeout}")
private int AOP_TIME_OUT;
/**
* 配置切入点表达式 : 指定哪些包中的类使用事务
*/
private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.gd.gd_service.service.impl.*.*(..)))";
//事务管理器
@Autowired
private TransactionManager transactionManager;
/**
* 全局事务配置
* REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
* SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
* MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
* REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
* NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
* NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
* NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED 。
* 指定方法:通过使用 propagation 属性设置,例如:@Transactional(propagation = Propagation.REQUIRED)
*/
@Bean
public TransactionInterceptor txAdvice(){
/**
* 配置事务管理规则
* 这里配置只读事务
* 查询方法, 只读事务,不做更新操作
*/
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
/**
* 增、删、改 需要的事务
* 必须带事务
* 当前存在事务就使用当前事务,当前不存在事务,就开启一个新的事务
*/
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
// 设置回滚规则:什么异常都需要回滚
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
// 当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 设置超时时间,超时则抛出异常回滚
requiredTx.setTimeout(AOP_TIME_OUT);
System.out.println("=====>>事务超时时间:"+requiredTx.getTimeout());
/**
* 无事务地执行,挂起任何存在的事务
*/
RuleBasedTransactionAttribute noTx = new RuleBasedTransactionAttribute();
noTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
/**
* 设置方法对应的事务
*/
Map<String, TransactionAttribute> methodMap = new HashMap<>();
// 可以提及事务或回滚事务的方法
//只读事务
methodMap.put("get*", readOnlyTx);
methodMap.put("query*", readOnlyTx);
methodMap.put("find*", readOnlyTx);
methodMap.put("list*", readOnlyTx);
methodMap.put("count*", readOnlyTx);
methodMap.put("exist*", readOnlyTx);
methodMap.put("search*", readOnlyTx);
methodMap.put("fetch*", readOnlyTx);
//写事务
methodMap.put("add*", requiredTx);
methodMap.put("save*", requiredTx);
methodMap.put("insert*", requiredTx);
methodMap.put("update*", requiredTx);
methodMap.put("modify*", requiredTx);
methodMap.put("delete*", requiredTx);
methodMap.put("creat*", requiredTx);
methodMap.put("edit*", requiredTx);
methodMap.put("remove*", requiredTx);
methodMap.put("repair*", requiredTx);
methodMap.put("binding*", requiredTx);
methodMap.put("clean*", requiredTx);
methodMap.put("upload*",requiredTx);
//无事务
methodMap.put("noTx*", noTx);
// 其他方法无事务,只读
methodMap.put("*", readOnlyTx);
//声明一个通过方法名字配置事务属性的对象
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.setNameMap(methodMap);
//返回事务拦截器
TransactionInterceptor txAdvice = new TransactionInterceptor(transactionManager, source);
return txAdvice;
}
@Bean(name = "txAdviceAdvisor")
public Advisor txAdviceAdvisor(TransactionInterceptor txAdvice) {
logger.info("=====>>创建txAdviceAdvisor");
//配置事务切入点表达式
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
//增强事务,关联切入点和事务属性
return new DefaultPointcutAdvisor(pointcut, txAdvice);
}
}
到了这里,关于Spring控制事务回滚的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!