网站备案怎么改,建工网论坛,辽宁建设工程信息网新入口,网站开发服务转包合同范本目录 Spring中事务的实现
MySQL中的事务使用
Spring 编程式事务
TransactionTemplate 编程式事务
TransactionManager编程式事务 Spring声明式事务
Transactional 参数说明
事务因为程序异常捕获不会自动回滚的解决方案
Transactional 原理 Spring 事务隔离级别
Spring…目录 Spring中事务的实现
MySQL中的事务使用
Spring 编程式事务
TransactionTemplate 编程式事务
TransactionManager编程式事务 Spring声明式事务
Transactional 参数说明
事务因为程序异常捕获不会自动回滚的解决方案
Transactional 原理 Spring 事务隔离级别
Spring事务失效的场景
非public修饰的方法
为什么非public修饰的方法使用Transactional会失效?
timeout超时
代码中有try/catch
调用类内部Transactional方法
数据库不支持事务
Spring 事务传播机制
事务隔离级别和传播机制有什么区别
① REQUIRES_NEW: 新建事务执行如果当前存在事务把当前事务挂起。
② REQUIRED 默认值:如果当前方法没有事务新建一个事务如果已经存在一个事务中则加入到这个事务中。 ③ NESTED嵌套事务如果当前存在事务则在嵌套事务内执行。如果不存在则执行与PROPAGATION_REQUIRED 类似的操作。
加入事务和嵌套事务有什么区别
嵌套事务的实现原理 Spring中事务的实现
Spring中的事务操作分为两类
编程式事务 手动写代码操作事务声明式事务利用注解自动开启和提交事务
MySQL中的事务使用
事务在MySQL有3个重要的操作开启事务、提交事务、回滚事务它们对应的操作命令如下
-- 开启事务
start transaction;
-- 业务执行-- 提交事务
commit;-- 回滚事务
rollback;
Spring 编程式事务
Spring 手动操作事务和上面MySQL操作事务类似他也是有3个重要操作步骤
开启事务(获取事务)提交事务回滚事务
编程式事务有两种实现方式
使用 TransactionTemplate 对象实现编程式事务使用更加底层的 TransactionManager 对象实现编程式事务
TransactionTemplate 编程式事务
要使用 TransactionTemplate 对象需要先将 TransactionTemplate 注入到当前类中然后再使用它提供的 execute 方法执行事务并返回相应的执行结果如果程序在执行途中出现了异常那么就可以使用代码手动回滚事务具体实现代码如下: TransactionManager编程式事务
TransactionManager 实现编程式事务相对麻烦一点它需要使用两个对象TransactionManager 的子类加上 TransactionDefinition 事务定义对象再通过调用TransactionManager的 getTransaction 获取并开启事务然后调用 TransactionManager 提供的 commit 方法提交事务或使用它的另一个方法 rollback 回滚事务它的具体实现代码如下:
SpringBoot内置了两个对象:
DataSourceTransactionManager: 事务的管理器 是用来获取事务开启事务、提交或回滚事务的。TransactionDefinition 事务的属性在获取事务的时候需要将TransactionDefinition传递进去从而获得一个事务 TransactionStatus。
实现代码如下 package com.example.demo.controller;import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;RestController // 这是Controller ResponseBody 的结果
//目的是返回非页面数据
RequestMapping(/user)
public class UserController {//编程式事务Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;Autowiredprivate TransactionDefinition transactionDefinition;Autowiredprivate UserService userService;RequestMapping(/del)public int del(Integer id) {if (id null || id 0) return 0;TransactionStatus transactionStatus null;int result 0;try {// 1. 开启事务transactionStatus dataSourceTransactionManager.getTransaction(transactionDefinition);// 业务操作 删除用户result userService.del(id);System.out.println(删除 result);dataSourceTransactionManager.commit(transactionStatus); // 提交事务} catch (Exception e) {if (transactionStatus ! null) {dataSourceTransactionManager.rollback(transactionStatus); // 回滚事务}}return result;}
}Spring声明式事务
声明式事务只需要事务的⽅法上添加Transactional 注解就可以实现了 ⽆需⼿动开启事务和提交事务添加该注解后实现的效果如下
进⼊⽅法时自动开启事务。方法执行完会自动提交事务。如果中途发⽣了没有处理的异常会自动回滚事务。
RestController
public class UserController2 {Autowiredprivate UserService userService;RequestMapping(/del)Transactionalpublic int del(Integer id) {if (id null || id 0) return 0;int result userService.del(id);System.out.println(删除: result);// int num 10 / 0; return result;}
} 如果上面的异常触发就会导致事务回滚如果没有事务则正常提交。
需要注意的一点是这里的Transactional 与单元测试中的Transactional不一样。
单元测试中的Transactional 是 无论方法是执行完方法后一定回滚事务。 Transactional的作用 Transactional 可以⽤来修饰方法或类:
修饰⽅法时: 只有修饰public⽅法时才⽣效(修饰其他⽅法时不会报错,也不生效)[推荐]修饰类时: 对 Transactional 修饰的类中所有的public方法都生效.
Transactional 参数说明 需要注意的是 Transactional 在异常被捕获的情况下不会进行事务自动回滚
package com.example.demo.controller;import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;RestController
RequestMapping(/user2)
public class UserController2 {Autowiredprivate UserService userService;RequestMapping(/del)Transactional()public int del(Integer id) {if (id null || id 0) return 0;int result userService.del(id);System.out.println(删除: result);try {int num 10 / 0;} catch (Exception e) {e.printStackTrace();}return result;}
}观察代码运行结果可以发现由于异常被程序捕获try-catch事务不会进行回滚数据被删除 其实这是因为 Transactional 在底层实现的时候是通过代理类来完成的代理类会通过反射拿到目标方法如果目标方法出现异常会进行回滚操作否则就提交当前事务。
因此如果我们在程序中显示的捕获异常那么 Transactional 里面的代理类就无法捕获到异常于是就提交了事务。
事务因为程序异常捕获不会自动回滚的解决方案 方案①将异常重新抛出 对于捕获的异常事务是会自动回滚的因此解决方案就是将异常重新抛出
package com.example.demo.controller;import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;RestController
RequestMapping(/user2)
public class UserController2 {Autowiredprivate UserService userService;RequestMapping(/del)Transactional()public int del(Integer id) {if (id null || id 0) return 0;int result userService.del(id);System.out.println(删除: result);try {int num 10 / 0;} catch (Exception e) {throw e;}return result;}
}运行结果 方案②手动回滚事务 ⼿动回滚事务在⽅法中使⽤ TransactionAspectSupport.currentTransactionStatus() 可 以得到当前的事务然后设置回滚⽅法 setRollbackOnly 就可以实现回滚了 Transactional 原理
Transactional 是基于 AOP 实现的AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝默认情况下会采⽤ JDK 的动态代理如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。 Transactional 在开始执⾏业务之前通过代理先开启事务在执⾏成功之后再提交事务。如果中途遇到的异常则回滚事务 实现细节 Spring 事务隔离级别
Spring 中事务隔离级别包含以下 5 种
Isolation.DEFAULT以连接的数据库的事务隔离级别为主。Isolation.READ_UNCOMMITTED读未提交可以读取到未提交的事务存在脏读。Isolation.READ_COMMITTED读已提交只能读取到已经提交的事务解决了脏读存在不可重复读。Isolation.REPEATABLE_READ可重复读解决了不可重复读但存在幻读MySQL默认级别。Isolation.SERIALIZABLE串⾏化可以解决所有并发问题但性能太低。
相比于 MySQL 的事务隔离级别Spring 的事务隔离级别只是多了⼀个 Isolation.DEFAULT以数据库的全局事务隔离级别为主。 Spring 中事务隔离级别只需要设置 Transactional ⾥的 isolation 属性即可具体实现代码如下 Spring事务失效的场景
在开始之前我们先要明确一个定义什么叫做“失效”
本文中的“失效”指的是“失去它的功效”也就是当 Transactional 不符合我们预期的结果时我们就可以说 Transactional 失效了。
那 Transactional 失效的场景有哪些呢接下来我们一一来看。
非public修饰的方法
当 Transactional 修饰的方法为非 public 时事务就失效了比如以下代码当遇到异常之后不能自动实现回滚
Transactional
RequestMapping(/save)
int save(UserInfo userInfo) {// 非空效验if (userInfo null ||!StringUtils.hasLength(userInfo.getUsername()) ||!StringUtils.hasLength(userInfo.getPassword()))return 0;// 执行添加操作int result userService.save(userInfo);System.out.println(add 受影响的行数 result);int num 10 / 0; // 此处设置一个异常return result;
}为什么非public修饰的方法使用Transactional会失效? 答: 这分为两层原因: 浅层原因 和 深层次原因.1. 浅层原因 浅层原因是 Transactional 源码限制了必须是 public 才能执行后续的代理流程它的部分实现源码如下:
protected TransactionAttribute computeTransactionAttribute(Method method, Class?
targetClass) {// Dont allow no-public methods as required.// 非 public 方法设置为 nul1if(allowPublicMethodsOnly() !Modifier.isPublic(method.getModifiers())) {return null;}// 后面代码省略....
}
2. 深层次原因 深层次的原因 Spring Boot 的动态代理只能代理公共方法而不能代理私有方法或受保护的方法。
这是因为 Spring 的动态代理是基于 Java 的接口代理机制或者基于 CGLib 库来实现的而这两种代理方式都只能代理公共方法。
接口代理: 当目标类实现了接口时Spring 使用JDK 动态代理来生成代理对象。JDK 动态代理是通过生成实现目标类接口的匿名类并将方法调用委托给目标类的实例来实现的。由于接口中的方法都是公共的所以JDK 动态代理只能代理公共方法。CGLib 代理: 当目标类没有实现接门时Spring 使用 CGLib 动态代理来生成代理对象CGLib 动态代理是通过生成目标类的子类并将方法调用委托给子类的实例来实现的。然而Java 中的继承要求子类能够继承父类的方法因此 CGLib 动态代理也只能代理目标类中的公共方法。 timeout超时
当在 Transactional 上设置了一个较小的超时时间时如果方法本身的执行时间超过了设置的 timeout 超时时间那么就会导致本来应该正常插入数据的方法执行失败示例代码如下
Transactional(timeout 3) // 超时时间为 3s
RequestMapping(/save)
int save(UserInfo userInfo) throws InterruptedException {// 非空效验if (userInfo null ||!StringUtils.hasLength(userInfo.getUsername()) ||!StringUtils.hasLength(userInfo.getPassword()))return 0;int result userService.save(userInfo);return result;
}UserService 的 save 方法实现如下
public int save(UserInfo userInfo) throws InterruptedException {// 休眠 5sTimeUnit.SECONDS.sleep(5);int result userMapper.add(userInfo);return result;
}代码中有try/catch
在前面 Transactional 的执行流程中我们提到当方法中出现了异常之后事务会自动回滚。然而如果在程序中加了 try/catch 之后Transactional 就不会自动回滚事务了示例代码如下
Transactional
RequestMapping(/save)
public int save(UserInfo userInfo) throws InterruptedException {// 非空效验if (userInfo null ||!StringUtils.hasLength(userInfo.getUsername()) ||!StringUtils.hasLength(userInfo.getPassword()))return 0;int result userService.save(userInfo);try {int num 10 / 0; // 此处设置一个异常} catch (Exception e) {}return result;
}调用类内部Transactional方法
当调用类内部的 Transactional 修饰的方法时事务是不会生效的示例代码如下
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;Service
public class MyService {// 这个outerMethod方法的Transactional无所谓有没有标注Transactionalpublic void outerMethod() {// 调用内部方法innerMethod();}Transactionalpublic void innerMethod() {// 这里的事务可能不会生效// ...}
}说明因为 Transactional 是基于动态代理实现的而当调用类内部的方法时不是通过代理对象完成的而是通过 this 对象实现的这样就绕过了代理对象从而事务就失效了。
数据库不支持事务
我们程序中的 Transactional 只是给调用的数据库发送了开始事务、提交事务、回滚事务的指令但是如果数据库本身不支持事务比如 MySQL 中设置了使用 MyISAM 引擎那么它本身是不支持事务的这种情况下即使在程序中添加了 Transactional 注解那么依然不会有事务的行为这就是巧妇也难为无米之炊吧。
Spring 事务传播机制
Spring 事务传播机制定义了多个包含了事务的⽅法: 相互调用时事务是如何在这些⽅法间进⾏传递的。
比如有两个⽅法A,B都被 Transactional 修饰,A⽅法调⽤B⽅法 A方法运行时,会开启⼀个事务.当A调⽤B时,B⽅法本⾝也有事务,此时B⽅法运⾏时,是加⼊A的事务,还是创建⼀个新的事务呢?这个就涉及到了事务的传播机制.
比如公司流程管理 执行任务之前,需要先写执行⽂档,任务执行结束,再写总结汇报。 此时A部门有⼀项⼯作,需要B部门的⽀援,此时B部门是直接使⽤A部门的⽂档,还是新建⼀个⽂档呢?
事务隔离级别和传播机制有什么区别
事务的隔离级别是保证多个并发事务执行的可控性稳定性而事务传播机制是保证一个事务在多个调用方法间的可控性稳定性。
事务隔离级别描述的是多个事务同时执行时的某种行为;
而事务传播机制是描述包含了多个事务的方法在相互调用时事务的传播行为。
所以事务隔离级别描述的是纵向事务并发调用时的行为模式而事务传播机制描述的是横向事务传递时的行为模式如下图所示: Spring 事务传播机制包含以下 7 种
Propagation.REQUIRED默认的事务传播级别它表示如果当前存在事务则加⼊该事务如果当前没有事务则创建⼀个新的事务。Propagation.SUPPORTS如果当前存在事务则加⼊该事务如果当前没有事务则以⾮事务的⽅式继续运⾏。Propagation.MANDATORYmandatory强制性如果当前存在事务则加⼊该事务如果当前没有事务则抛出异常。Propagation.REQUIRES_NEW表示创建⼀个新的事务如果当前存在事务则把当前事务挂起。也就是说不管外部⽅法是否开启事务Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启⾃⼰的事务且开启的事务相互独⽴互不⼲扰。Propagation.NOT_SUPPORTED以⾮事务⽅式运⾏如果当前存在事务则把当前事务挂起。Propagation.NEVER以⾮事务⽅式运⾏如果当前存在事务则抛出异常。Propagation.NESTED如果当前存在事务则创建⼀个事务作为当前事务的嵌套事务来运行如果当前没有事务则该取值等价于 PROPAGATION_REQUIRED。
以上 7 种传播⾏为可以根据是否⽀持当前事务分为以下 3 类 为了加深印象我们这里就演示其中的三种传播机制
调用链关系Controller -- UserService - LogService
Controller UserService LogService 方便演示这里更改传播机制时候是整个调用链一起修改。 ① REQUIRES_NEW: 新建事务执行如果当前存在事务把当前事务挂起。 预期运行结果用户添加成功日志添加失败。
实际运行结果没有添加任何记录。 分析因为没有将程序异常进行处理导致整条调用链上的事务感知到从而全部回滚。 改进方法将异常捕获进行回滚 运行结果符合预期日志出现回滚用户加入操作没有回滚。 ② REQUIRED 默认值 :如果当前方法没有事务新建一个事务如果已经存在一个事务中则加入到这个事务中。 预期运行结果全部事务都进行回滚。
运行结果全部事务进行回滚。
这里即使我们进行异常处理还是出现报错的原因是因为这里使用REQUIRED 是把这些事务看成一个整体而事务外部觉得不应该回滚因为没有出现异常而事务内部进行回滚了所以这里程序不知道什么情况所以报500了。 但是如果是外部事务进行回滚那么内部事务也会进行回滚。这时候不会报异常 ③ NESTED嵌套事务如果当前存在事务则在嵌套事务内执行。如果不存在则执行与PROPAGATION_REQUIRED 类似的操作。 执行结果日志添加失败用户添加成功。
这里的效果跟REQUIRES_NEW是很像的。 看到这里可能有人会问 加入事务和嵌套事务有什么区别 加入事务 (REQUIRED) 和嵌套事务(NESTED) 都是事务传播机制中的两种传播级别如果当前不存在事务那么二者的行为是一致的。但如果当前存在事务:
加入事务: 遇到异常时会回滚全部事务嵌套事务:遇到异常时回滚部分事务
嵌套事务之所以能回滚部分事务是因为数据库中存在一个保存点的概念嵌套事务相对于新建了一个保存点如果出现异常了那么只需要回滚到保存点即可这样就实现了部分事务的回滚。
嵌套事务的实现原理
嵌套事务之所以能实现部分事务的回滚是因为在数据库中存在一个保存点(savepoint)的概念以 MySQL 为例嵌套事务相当于新建了一个保存点而滚回时只回滚到当前保存点因此之前的事务是不受影响的这一点可以在 MySQL 的官方文档汇总找到相应的资料: https://dev.mysgl.com/doc/refman/5.7/en/savepoint.html
而 REQUIRED 是加入到当前事务中并没有创建事务的保存点因此出现了回滚就是整个事务回滚这就是嵌套事务和加入事务的区别。 保存点就像玩通关游戏时的“游戏存档”一样如果设置了游戏存档那么即使当前关卡失败了也能继续上一个存档点继续玩而不是从头开始玩游戏。