一、思维导图


Spring 事务管理分为编程式和声明式两种。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体的逻辑与事务处理解耦。

声明式事务有两种方式,一种是在配置文件(XML)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。

默认配置下 Spring 只会回滚运行时、未检查异常(继承自 RuntimeException 的异常)或者 Error。
@Transactional 注解只能应用到 public 方法才有效。

1.1 声明式事务管理实现方式

基于tx和aop名字空间的xml配置文件

// 基本配置
<bean name="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="shardingDataSource"></property></bean><tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />// MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用
// <annotation-driven>标签的声明,则是在Spring内部启用@Transactional来进行事务管理,使用 @Transactional 前需要配置

BPM项目中applicationContext.xml事务配置如下

 <!-- 定义事务管理器 --><bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactory" /></bean><!-- 申明annotation 加载事务驱动 --><tx:annotation-driven transaction-manager="txManager" proxy-target-class="true" /><!-- hibernate4必须配置为开启事务 否则 getCurrentSession()获取不到 --><tx:advice id="txAdvice" transaction-manager="txManager"><tx:attributes><tx:method name="save*" propagation="REQUIRED" /><tx:method name="import*" propagation="REQUIRED" /><tx:method name="add*" propagation="REQUIRED" /><tx:method name="create*" propagation="REQUIRED" /><tx:method name="insert*" propagation="REQUIRED" /><tx:method name="edit*" propagation="REQUIRED" /><tx:method name="update*" propagation="REQUIRED" /><tx:method name="parse*" propagation="REQUIRED" /> <tx:method name="merge*" propagation="REQUIRED" /><tx:method name="del*" propagation="REQUIRED" /><tx:method name="remove*" propagation="REQUIRED" /><tx:method name="put*" propagation="REQUIRED" /><tx:method name="use*" propagation="REQUIRED" /><tx:method name="start*" propagation="REQUIRED" /><tx:method name="stop*" propagation="REQUIRED" /><tx:method name="set*" propagation="REQUIRED" /><tx:method name="execute*" propagation="REQUIRED"/><tx:method name="get*" propagation="REQUIRED" read-only="true" /><tx:method name="count*" propagation="REQUIRED" read-only="true" /><tx:method name="find*" propagation="REQUIRED" read-only="true" /><tx:method name="list*" propagation="REQUIRED" read-only="true" /> <tx:method name="*" propagation="REQUIRED" read-only="true"  /></tx:attributes></tx:advice><aop:config expose-proxy="true"><!-- 只对业务逻辑层实施事务 --><aop:pointcut id="txPointcut"expression="(execution(* com.gzsolartech.smartforms.service..*.*(..))|| execution(* com.gzsolartech.smartforms.ldap..*.*(..))|| execution(* com.gzsolartech.bpmportal.service..*.*(..))|| execution(* com.gzsolartech.portal.service..*.*(..))|| execution(* com.gzsolartech.smartforms.webload.DatDocumentSaveInterceptor.*(..))|| execution(* com.gzsolartech.bpmportal.service.extend.ExtendDatDocumentService.*(..)))" /><aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /></aop:config><bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"><!-- <bean id="sessionFactory" class="org.springframework.orm.hibernate4.HibernateTemplate"> --><property name="dataSource"><ref bean="dataSource"/></property><property name="hibernateProperties"><!-- 从配置文件中得到各种属性的值,更改的时候只需要改变配置文件从的值即可 --><props><prop key="hibernate.dialect">${hibernate.dialect}</prop><prop key="hibernate.show_sql">${hibernate.show_sql}</prop><prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop><prop key="hibernate.format_sql">${hibernate.format_sql}</prop><prop key="hibernate.connection.SetBigStringTryClob">${hibernate.connection.SetBigStringTryClob}</prop><prop key="hibernate.temp.use_jdbc_metadata_defaults">false</prop><!-- <prop key="hibernate.current_session_context_class">thread</prop> --><!-- 开启查询缓存 --><prop key="hibernate.cache.use_query_cache">true</prop><!-- 开启二级缓存 --><prop key="hibernate.cache.use_second_level_cache">true</prop><!-- 高速缓存提供程序 --> <!-- 由于spring也使用了Ehcache, 保证双方都使用同一个缓存管理器 --><prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</prop><!-- 用于oracle指定用户名(主要用于更新某个用户的表) --><prop key="hibernate.default_schema">${database.username}</prop><!-- <prop key="hibernate.default_schema">aacsmart</prop>  --></props></property><property name="packagesToScan"><list><value>com.gzsolartech.smartforms.entity</value><value>com.gzsolartech.portal.entity</value><value>com.gzsolartech.bpmportal.entity</value><value>com.gzsolartech.smartforms.entity.bpm</value></list></property></bean>

1.2 基于@Transactional注解

@Transactional实质是使用了JDBC的事务来进行事务控制的
@Transactional基于Spring的动态代理的机制

1.3 @Transactional实现原理

1:事务开始时,通过AOP机制,生成一个代理connection对象,并将其放入DataSource实例的某个与DataSourceTransactionManager相关的某处容器中。

在接下来的整个事务中,客户代码都应该使用该connection连接数据库,执行所有数据库命令[不使用该connection连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚](物理连接connection逻辑上新建一个会话session;DataSource与TransactionManager配置相同的数据源)

2:事务结束时,回滚在第1步骤中得到的代理connection对象上执行的数据库命令,然后关闭该代理connection对象(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)

二、Spring事务特性

Spring所有的事务管理策略类都继承自org.springframework.transaction.PlatformTransactionManager接口

public interface PlatformTransactionManager {TransactionStatus getTransaction(TransactionDefinition definition)throws TransactionException;void commit(TransactionStatus status) throws TransactionException;void rollback(TransactionStatus status) throws TransactionException;
}

三、事务的隔离级别

是指若干个并发的事务之间的隔离程度

@Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE):串行化

四、事务传播行为

4.1 事务传播机制

如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为

TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

嵌套事务:带有事务的方法调用其他事务的方法,此时执行的情况取决配置的事务的传播属性
PROPAGATION_REQUIRES_NEW
启动一个新的,不依赖于环境的 “内部” 事务。这个事务将被完全 commited 或 rolled back 而不依赖于外部事务,它拥有自己的隔离范围,自己的锁等等。当内部事务开始执行时,外部事务将被挂起,内务事务结束时,外部事务将继续执行。
PROPAGATION_NESTED
如果外部事务 commit,嵌套事务也会被commit;如果外部事务roll back,嵌套事务也会被roll back。
开始一个 “嵌套的” 事务,它是已经存在事务的一个真正的子事务。嵌套事务开始执行时,它将取得一个 savepoint。如果这个嵌套事务失败,我们将回滚到此 savepoint。嵌套事务是外部事务的一部分,只有外部事务结束后它才会被提交。

4.2 @Transactional 的 propagation属性代码示例

比如如下代码,UserService 方法首先调用 updateUserById方法,再调用insertUser方法,但是抛出了异常,就会导致事务回滚,如下updateUserById和insertUser都不会插入数据库。

@Service
public class UserService {//更新@Transactional(propagation = Propagation.REQUIRED)public boolean updateUserById(User user) {userMapper.updateUserById(user);if(true) {throw new RuntimeException("updateUserById 抛异常了");}insertUser();return true;}//新增public boolean insertUser() {/*//普通for循环插入for (int i = 0; i <20; i++) {User user = new User();user.setId(i);user.setUserName("testusername" + i);user.setPassWord("testpassword" + i);user.setRealName("testrealname"+i);userMapper.insertUser(user);}*///userMapper.insertUser(user);List<User> list = new ArrayList<>();for (int i = 0; i <2; i++) {User user = new User();user.setId(i);user.setUserName("*username" + i);user.setPassWord("*password*"+i);user.setRealName("*realname*"+i);list.add(user);}userMapper.insertUser(list);return true;}
}

访问http://localhost:8086/testBoot/updateUser?id=1&realName=zhangsannfeng
报错如下

现在有需求如下,就算UserService 方法的后面抛异常了,也不能影响 updateUserById方法的数据插入。
为了解决这个问题,我们可以新建一个类OtherService:

package org.spring.springboot.service;import java.util.ArrayList;
import java.util.List;import org.spring.springboot.entity.User;
import org.spring.springboot.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;@Service
public class OtherService  {@AutowiredUserMapper userMapper;//新增@Transactional(propagation = Propagation.REQUIRES_NEW)public boolean insertUser() {/*//普通for循环插入for (int i = 0; i <20; i++) {User user = new User();user.setId(i);user.setUserName("testusername" + i);user.setPassWord("testpassword" + i);user.setRealName("testrealname"+i);userMapper.insertUser(user);}*///userMapper.insertUser(user);List<User> list = new ArrayList<>();for (int i = 10; i <15; i++) {User user = new User();user.setId(i);user.setUserName("*username" + i);user.setPassWord("*password*"+i);user.setRealName("*realname*"+i);list.add(user);}userMapper.insertUser(list);return true;}
}

而UserService更新如下

  //更新@Transactional(propagation = Propagation.REQUIRED)public boolean updateUserById(User user) {userMapper.updateUserById(user);otherService.insertUser();if(true) {throw new RuntimeException("updateUserById 抛异常了");}    return true;}

执行前数据库查询所有数据

执行后http://localhost:8086/testBoot/updateUser?id=1&realName=zhangsanfeng结果如下:

后台控制台报错

可以看到,UserService类中userMapper.updateUserById(user);方法未生效,事务回滚,而otherService.insertUser();方法生效。

五、Spring事务回滚规则

指Spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。
Spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

默认配置下,Spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
用Spring事务管理器,由Spring来负责数据库的打开,提交,回滚.默认遇到运行期例外(throw new RuntimeException(“注释”);)会回滚,即遇到不受检查(unchecked)的例外时回滚;而遇到需要捕获的例外(throw new Exception(“注释”);)不会回滚,即遇到受检查的例外(就是非运行时抛出的异常,编译器会检查到的异常叫受检查例外或说受检查异常)时,需我们指定方式来让事务回滚要想所有异常都回滚,要加上 @Transactional( rollbackFor={Exception.class,其它异常}) .如果让unchecked例外不回滚: @Transactional(notRollbackFor=RunTimeException.class)

六、注意事项

@Transactional 使用位置类上方、方法上方
Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效
当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

方法的访问权限为 public
@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常

默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

例如一:同一个类中方法,A方法未使用此标签,B使用了,C未使用,A 调用 B , B 调用 C ;则外部调用A之后,B的事务是不会起作用的
例如二:若是有上层(按照 Controller层、Service层、DAO层的顺序)由Action 调用 Service 直接调用,发生异常会发生回滚;若间接调用,Action 调用 Service 中 的A 方法,A无@Transactional 注解,B有,A调用B,B的注解无效。

参考文章
https://blog.csdn.net/mingyundezuoan/article/details/79017659
https://blog.csdn.net/weixin_39845113/article/details/110510730

Spring事务@Transactional注解原理相关推荐

  1. Spring的@Transactional注解踩坑

    @Transactional介绍 Spring为开发人员提供了声明式事务的使用方式,即在方法上标记@Transactional注解来开启事务.大家在日常的开发中很多业务代码对数据进行操作的时候一定是希 ...

  2. 《深入理解分布式事务》第三章 Spring 事务的实现原理

    <深入理解分布式事务>第三章 Spring 事务的实现原理 文章目录 <深入理解分布式事务>第三章 Spring 事务的实现原理 一.Spring 事务原理 1.JDBC 直接 ...

  3. Spring事务及其注解

    Spring事务及其注解目录页 Spring事务相关 Spring事务的本质 Spring和事务的关系 Spring事务三要素 Spring事务的注解配置 事务注解的本质 Spring事务的表达方式 ...

  4. Spring事务传播实现原理

    什么是事务传播? 假设这样一个场景:方法A上面添加了一个@Transactional注解,在该方法中去调用另一个Service的方法B,但方法B并不需要事务,但是由于A开启了事务,导致B方法的执行也处 ...

  5. Spring事务常用注解

    文章目录 Spring事务常用注解 1. @EnableTransactionMannagement 1.1 作用 1.2 属性分析 2. @Transactional 2.1 作用 2.2 属性分析 ...

  6. Spring的Transactional注解

    Spring的Transactional注解主要有以下功能: 1. 标注在方法上,如果该方法掉了多个别的方法,每个方法都有对数据库做数据更改,如果这些更改需要保持一致性,这时就可以用到这个注解. 2. ...

  7. @transactional注解原理_《Spring源码解析(十二)》深入理解Spring事务原理,告别面试一问三不知的尴尬...

    本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让 ...

  8. 框架源码专题:Spring声明式事务Transactional的原理

    文章目录 1. @Transactional的使用 2. spring事务的原理 2.1 开启事务,注册bean的后置处理器和相关bean对象,封装Advisor 2.2 匹配并创建动态代理 2.3 ...

  9. SSM框架中使用Spring的@Transactional注解进行事务管理

    一 介绍 在企业级应用中,保护数据的完整性是非常重要的一件事.因此不管应用的性能是多么的高.界面是多么的好看,如果在转账的过程中出现了意外导致用户的账号金额发生错误,那么这样的应用程序也是不可接受的 ...

  10. 面试一口气说出Spring的声明式事务@Transactional注解的6种失效场景

    一.Spring事务管理的两种方式 事务管理在系统开发中是不可缺少的一部分,Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种. 编程式事务:是指在代码中手动的管理事务的提交.回滚 ...

最新文章

  1. Scalable IO in Java
  2. FreeSWITCH与PSTN对接
  3. STM32H743+CubeMX-QSPI读写外部FLASH(W25Q128JVSQ)
  4. C# Unity依赖注入
  5. java编译环境有问题_java: 用JCeator编译器,编译出现问题怎么解决?
  6. 详解Oracle临时表的几种用法及意义
  7. linux fastboot 工具下载,linuxadb fastboot 和VNC工具的安装使用
  8. 《C++ Primer 第5版》-13.3交换操作-康奈尔笔记
  9. java计算机毕业设计校园讲座管理源码+系统+lw文档+mysql数据库+部署
  10. 2021-5-25有限元从0开始第六天(Lp空间,弱导数)
  11. CAD小问题解决办法1
  12. 写作三件套(VScode Miktex Latex Workshop)入门三大坑
  13. 消费者洞察案例分析_情绪搜索洞察定时器案例研究
  14. python设计函数isleapyear_Python函数
  15. Java堆外内存:堆外内存回收方法
  16. 移动端测试=== adb 无线连接手机
  17. 前端vue使用XXTEA进行对称加解密。同时对比rsa算法和xxtea算法的优缺点。
  18. JavaScript和Java求字符串的字节长度
  19. C# 文件路径-Window服务
  20. 2023秋招--腾讯天美--游戏客户端--三面面经

热门文章

  1. kali中安装使用msfconsole
  2. Unity动作游戏大全
  3. Unity游戏开始界面制作教学
  4. 51单片机之CHQ1838红外接收(NEC协议)
  5. Dilated Convolution(空洞卷积、膨胀卷积)详解
  6. itext 生成 PDF
  7. itext pdf合并
  8. Oracle 创建和操作表
  9. TM1640操作源码--LED驱动IC
  10. 十次方:机架式服务器和塔式服务器有什么区别?