1、Spring中开启事务的方式主要有两种:编程式事务和声明式事务。
2、事务是我们开发过程中经常会使用到的,为了在业务执行过程中出现异常时,回滚到原始状态。而事务的回滚在大多数情况下都是靠着 exception(异常)来触发回滚的,当事务机制捕捉到异常,就会开始回滚。
3、但往往也会出现情况:在业务代码中,需要对异常单独进行处理,异常不会抛出,但需要事务回滚的情况,这个时候就需要手动调用回滚
@Transactional 声明式事务,是开发过程中最常用的开启事务的方式。
也可以通过切面,对整个业务层的方法进行事务控制
优点:使用方便,而且对代码的侵入性低
这种方式,默认是通过 捕获 RunTimeException 来触发事务回滚,只要是 RuntimeException 以及 它的子类,都可以触发事务回滚。也可以修改触发事务回滚的异常范围,可以通过修改 @Transactional 中的 rollbackFor 属性,修改异常范围。比如:
@Transactional(rollbackFor = Exception.class),修改完成后,只要是 Exception 类的子类,都可以触发事务回滚
有时候需要对异常进行特殊处理,异常被捕获无法抛出时,声明式事务就失效不可用。
示例:抛出异常被捕获,无法触发事务回滚。
//业务代码 try{ //业务处理出现异常 } catch (Exception e) { // 捕获异常,打印异常,或其他处理。但不抛出新的异常 e.printStackTrace(); //可以将捕获后的异常,封装为新的业务异常抛出(正常回滚) throw new xxxxException(e.getMessage); } //此时return语句能够执行 return xxx;
此处,可以将捕获后的异常,封装为新的业务异常抛出
在开发过程中,我们需要同时进行多个对数据的操作,这时候需要使用事务去控制多个操作的一致性。
通过声明式事务**@Transactional** 注解修饰方法的形式开启事务,需要注意到以下这些情况会出现事务失效。
例如我们使用Mysql数据,在 5.5 版本之前,Mysql 默认的数据引擎都是 MyISAM 的,而 MyISAM 是不支持事务的;在 5.5 版本之后,默认的数据引擎是 InnoDB 的,它是支持事务的。
Spring 对事务的管理实现又是基于数据库的,所以当数据库引擎不支持事务的时候,自然就不会有起作用的事务机制了,所有我们在选择数据库的时候,需要去选择支持事务的数据引擎。
当添加了注解或者全局事务配置了路径,但是需要用到事务的方法所在的类没有注入到Spring容器中,这样事务也不会生效,通常我们都是添加到业务逻辑处理层,通常都是添加@Service,将当前类注入到Spring中。
@Transactional注解应用在非public修饰的方法上,Transactional将会失效。这是因为在Spring AOP代理时,事务拦截器只能拦截public方法,而非public方法将直接被执行,不会经过代理。因此,建议将@Transactional注解只应用在public方法上。
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 异常"); } }
设置 @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。
调用本类方法导致传播行为失效,同一个 Service 的两个方法之间调用,就会出现这个问题,原因还是在代理对象这里,我们期待的调用是一个代理类的调用,但是我们若是直接在方法中内部调用,不好意思,被调用的方法的事务失效,没有被 AOP 增强。
**示例:**方法addStudents调用本类中的方法addStudent
@Service public class StudentService { @Transactional public void addStudent(Student student) { // do something } public void addStudents(Liststudents) { 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(Liststudents) { 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(Liststudents) { for (Student student : students) { // 这里在异步线程中调用同类的方法,事务会生效 addStudent(student); } } }
事务机制的回滚,是 通过异常来触发事务回滚的。在开发过程中,会出现异常被捕获处理了,而且没有再抛出新的异常,就会导致异常丢失,无法触发回滚。
**示例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; } }
Spring事务控制手动回滚是指在某些情况下,我们需要主动触发事务的回滚,而不是依赖于Spring的默认回滚策略。Spring的默认回滚策略是对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作。有两种常用的方法可以实现手动回滚:
- 手动抛出一个运行时异常,例如throw new RuntimeException(),这样Spring就会捕获到这个异常,并执行事务回滚。
- 通过Spring提供的事务切面支持类TransactionAspectSupport,调用其currentTransactionStatus().setRollbackOnly()方法,这样就可以标记当前事务为回滚状态,而不需要抛出异常。
使用手动回滚的方法时,需要注意以下几点:
- 被回滚的方法必须使用@Transactional注解,否则无法开启事务管理,也就无法回滚。
- 被回滚的方法不能在同一个类中直接调用,否则无法走代理类,也就无法回滚,需要通过AOP代理调用或者注入自身的bean来调用。
- 被回滚的方法不能使用try catch来捕获异常,否则无法触发回滚,需要在方法签名上声明throws或者在catch块中重新抛出异常。
**示例:**手动回滚事务
//假设这是一个service类的片段 try{ //出现异常 } catch (Exception e) { // 捕获异常,打印异常,或其他处理。但不抛出新的异常 e.printStackTrace(); // 手动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } //此时return语句能够执行 return xxx;
示例:
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); /** * 设置方法对应的事务 */ MapmethodMap = 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); } }