尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Spring事务传播机制与隔离级别详解

Spring事务传播机制与隔离级别详解 文章目录1. Spring中事务的实现1.1 声明式事务Transactional2. Transactional详解2.1 rollbackFor2.2. 事务隔离级别3. 事务传播机制3.1 事务传播机制有哪些3.2 Spring事务传播机制的使用3.2.1 REQUIRED(加入事务)3.2.2. REQUIRES_NEW(新建事务)3.2.3 NESTED和REQUIRED 有什么区别?1. Spring中事务的实现事务是一组操作的集合, 是一个不可分割的操作事务会把所有的操作作为一个整体, 一起向数据库提交或者是撤销操作请求. 所以这组操作要么同时成功, 要么同时失败.事务的操作主要有三步:开启事务start transaction (一组操作前开启事务)提交事务: commit (这组操作全部成功, 提交事务)回滚事务: rollback (这组操作中间任何一个操作出现异常, 回滚事务)Spring中的事务操作分两类:编程式事务(手动写代码操作事务)声明式事务(利用注解自动开启和提交事务)编程式事务感兴趣可自行了解, 本文主要介绍声明式事务1.1 声明式事务TransactionalRequestMapping(/trans)RestControllerpublicclassTransactionalController{AutowiredprivateUserServiceuserService;TransactionalRequestMapping(/registry)Stringregistry(Stringname,Stringpassword){//用户注册userService.registryUser(name,password);inta10/0;// 强制抛出异常return注册成功;}}此时数据库却没有新增数据, 事务进行了回滚我们一般会在业务逻辑层当中来控制事务, 因为在业务逻辑层当中, 一个业务功能可能会包含多个数据访问的操作. 在业务逻辑层来控制事务, 我们就可以将多个数据访问操作控制在一个事务范围内, 这里写在Controller只是方便演示Transactional可以用来修饰方法, 也能修饰类修饰方法时: 只有修饰public 方法时才生效(修饰其他方法时不会报错, 也不生效)[推荐]修饰类时:对 Transactional 修饰的类中所有的 public 方法都生效方法/类被 Transactional 注解修饰时, 在目标方法执行开始之前, 会自动开启事务, 方法执行结束之后, 自动提交事务;如果在方法执行过程中, 出现异常, 且异常未被捕获, 就进行事务回滚操作;如果异常被程序捕获, 方法就被认为是成功执行, 依然会提交事务, 不会回滚;我们也可以手动回滚事务, 使用TransactionAspectSupport.currentTransactionStatus() 得到当前的事务, 并使用 setRollbackOnly, 可以写在try-catch中try{//强制程序抛出异常inta10/0;}catch(Exceptione){// 手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}2. Transactional详解我们学习了 Transactional 的基本使用. 接下来我们学习 Transactional注解的使用细节.我们主要学习 Transactional 注解当中的三个常见属性:rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型Isolation: 事务的隔离级别. 默认值为 Isolation.DEFAULTpropagation: 事务的传播机制. 默认值为 Propagation.REQUIRED2.1 rollbackForTransactional 默认只在遇到运行时异常和Error时才会回滚, 非运行时异常不回滚. 即Exception的子类中, 除了RuntimeException及其子类我们修改下代码(IOException不属于RuntimeException)TransactionalRequestMapping(/r2)publicStringr2(Stringname,Stringpassword)throwsIOException{//用户注册userService.registryUser(name,password);log.info(用户数据插入成功);if(true){thrownewIOException();}returnr2;}此时虽然程序抛出了异常, 但是事务仍然进行了提交, 数据库中任然插入成功如果我们需要所有异常都回滚, 需要来配置 Transactional 注解当中的 rollbackFor 属性, 通过 rollbackFor 这个属性指定出现何种异常类型时事务进行回滚Transactional(rollbackForException.class)2.2. 事务隔离级别先回顾下MySQL的事务隔离级别:读未提交(READ UNCOMMITTED):也叫未提交读. 该隔离级别的事务可以看到其他事务中未提交的数据;因为其他事务未提交的数据可能会发生回滚, 但是该隔离级别却可以读到, 我们把该级别读到的数据称之为脏数据, 这个问题称为脏数;读已提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数据;该隔离级别不会有脏读的问题.但由于在事务的执行中可以读取到其他事务提交的结果, 所以在不同时间的相同 SQL 查询可能会得到不同的结果, 这种现象叫做不可重复读;可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也就可以确保同一事务多次查询的结果一致, 但是其他事务新插入的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读 是 MySQL 的默认事务隔离级别;此级别的事务正在执行时, 另一个事务成功的插入了某条数据, 但因为它每次查询的结果都是一样的, 所以会导致查询不到这条数据, 自己重复插入时又失败(因为唯一约束的原因). 明明在事务中查询不到这条信息但自己就是插入不进去, 这个现象叫幻读串行化(SERIALIZABLE): 序列化, 事务最高隔离级别. 它会强制事务排序, 使之不会发生冲突, 从而解决了脏读, 不可重复读和幻读问题, 但因为执行效率低, 所以真正使用的场景并不多事务隔离级别脏读不可重复读幻读读未提交√√√读已提交×√√可重复读××√串行化×××Spring的事务隔离机制有五种:Isolation.DEFAULT: 以连接的数据库的事务隔离级别为主.Isolation.READ_UNCOMMITTED: 读未提交, 对应SQL标准中 READ UNCOMMITTEDIsolation.READ_COMMITTED: 读已提交,对应SQL标准中 READ COMMITTEDIsolation.REPEATABLE_READ: 可重复读, 对应SQL标准中 REPEATABLE READIsolation.SERIALIZABLE: 串行化, 对应SQL标准中 SERIALIZABLESpring 中事务隔离级别可以通过 Transactional 中的 isolation 属性进行设置Transactional(isolationIsolation.READ_COMMITTED)3. 事务传播机制事务传播机制: 多个事务方法存在调用关系时, 事务是如何在这些方法间进行传播的.比如有两个方法A, B都被 Transactional 修饰, A方法调用B方法A方法运行时, 会开启一个事务. 当A调用B时, B方法本身也有事务, 此时B方法运行时, 是加入A的事务, 还是创建一个新的事务呢?事务隔离级别解决的是多个事务同时调用一个数据库的问题;事务传播机制解决的是一个事务在多个节点(方法)中传递的问题3.1 事务传播机制有哪些Transactional 注解支持事务传播机制的设置, 通过 propagation 属性来指定传播行为Spring 事务传播机制有以下 7 种:Propagation.REQUIRED: 默认的事务传播级别. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则创建一个新的事务;Propagation.SUPPORTS: 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则以非事务的方式继续运行;Propagation.MANDATORY: 强制性. 如果当前存在事务, 则加入该事务. 如果当前没有事务, 则抛出异常;Propagation.REQUIRES_NEW: 创建一个新的事务. 如果当前存在事务, 则把当前事务挂起. 也就是说不管外部方法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部方法都会新开启自己的事务, 且开启的事务相互独立, 互不干扰.Propagation.NOT_SUPPORTED: 以非事务方式运行, 如果当前存在事务, 则把当前事务挂起(不用).Propagation.NEVER:以非事务方式运行, 如果当前存在事务, 则抛出异常;Propagation.NESTED: 如果当前存在事务, 则创建一个事务作为当前事务的嵌套事务来运行.如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED3.2 Spring事务传播机制的使用对于以上事务传播机制我们重点关注以下两个就可以了:REQUIRED默认值REQUIRES_NEW3.2.1 REQUIRED(加入事务)以下代码:用户注册, 插入数据记录操作日志, 插入数据(出现异常)观察propagation Propagation.REQUIRED 执行结果对应的UserService和LogService操作都添加上了Transactional(propagation Propagation.REQUIRED)RequestMapping(/propaga)RestControllerpublicclassPropagationController{AutowiredprivateUserServiceuserService;AutowiredprivateLogServicelogService;Transactional(propagationPropagation.REQUIRED)RequestMapping(/p1)publicStringp1(Stringname,Stringpassword){//用户注册userService.registryUser(name,password);//记录操作日志logService.insertLog(name,用户注册);returnr3;}}Slf4jServicepublicclassUserService{AutowiredprivateUserInfoMapperuserInfoMapper;Transactional(propagationPropagation.REQUIRED)publicvoidregistryUser(Stringname,Stringpassword){//插入用户信息userInfoMapper.insert(name,password);}}ServiceSlf4jpublicclassLogService{AutowiredprivateLogInfoMapperlogInfoMapper;Transactional(propagationPropagation.REQUIRED)publicvoidinsertLog(Stringname,Stringop){inta10/0;//记录用户操作logInfoMapper.insertLog(name,用户注册);}}结果:p1方法开始执行, 开启事务用户注册, 插入数据, 执行成功(此操作的事务和p1使用同一个)记录操作日志, 插入一条数据, 出现异常, 执行失败(同样和p1 使用同一个事务)第三步出现错误, 事务回滚, 步骤2和3使用同一个事务, 所以步骤2的数据也回滚3.2.2. REQUIRES_NEW(新建事务)将上述UserService 和LogService 中相关方法事务传播机制改为Propagation.REQUIRES_NEWRequestMapping(/propaga)RestControllerpublicclassPropagationController{AutowiredprivateUserServiceuserService;AutowiredprivateLogServicelogService;Transactional(propagationPropagation.REQUIRES_NEW)RequestMapping(/p1)publicStringp1(Stringname,Stringpassword){//用户注册userService.registryUser(name,password);//记录操作日志logService.insertLog(name,用户注册);returnr3;}}Slf4jServicepublicclassUserService{AutowiredprivateUserInfoMapperuserInfoMapper;Transactional(propagationPropagation.REQUIRES_NEW)publicvoidregistryUser(Stringname,Stringpassword){//插入用户信息userInfoMapper.insert(name,password);}}ServiceSlf4jpublicclassLogService{AutowiredprivateLogInfoMapperlogInfoMapper;Transactional(propagationPropagation.REQUIRES_NEW)publicvoidinsertLog(Stringname,Stringop){inta10/0;//记录用户操作logInfoMapper.insertLog(name,用户注册);}}运行程序, 发现用户数据插入成功了, 日志表数据插入失败LogService 方法中的事务不影响 UserService 中的事务当我们不希望事务之间相互影响时, 可以使用该传播行为3.2.3 NESTED和REQUIRED 有什么区别?整个事务如果全部执行成功, 二者的结果是一样的;如果事务一部分执行成功, REQUIRED加入事务会导致整个事务全部回滚. NESTED嵌套事务可以实现局部回滚, 不会影响上一个方法中执行的结果.嵌套事务之所以能够实现部分事务的回滚, 是因为事务中有一个保存点(savepoint)的概念, 嵌套事务进入之后相当于新建了一个保存点, 而滚回时只回滚到当前保存点.REQUIRED 是加入到当前事务中, 并没有创建事务的保存点, 因此出现了回滚就是整个事务回滚, 这就是嵌套事务和加入事务的区别
返回列表