一、Spring声明式事务用法

1、在spring配置文件中配置事务管理器

  <bean id="baseDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"
abstract="true">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="initialSize" value="2"/>
<property name="minIdle" value="2"/>
<property name="maxActive" value="20"/>
<property name="maxWait" value="10000"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
<property name="minEvictableIdleTimeMillis" value="300000"/>
<property name="validationQuery" value="SELECT 'x' FROM DUAL"/>
<property name="testWhileIdle" value="true"/>
<property name="testOnBorrow" value="false"/>
<property name="testOnReturn" value="false"/>
<property name="filters" value="stat"/>
</bean>
<!-- 搜索系统数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"
parent="baseDataSource">
<property name="url" value="${jdbc.url.search}"/>
<property name="username" value="${jdbc.user.search}"/>
<property name="password" value="${jdbc.pwd.search}"/>
</bean>
  
  <!-- 配置事务管理器 -->
  <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"></property>
  </bean>   <!-- 使得事务注解生效 -->
  <tx:annotation-driven transaction-manager="transactionManager"/>

  事务管理器需要注入一个DataSource接口类型的数据源,并且需要开启注解驱动。

2、在需要使用事务管理的方法前加上@Transactional注解

  @Transactional
public void updatePoiRank(PoiRank rank) {
poiRankDAO.updatePoiRank(rank);
}

3、使用注意事项

  (1)@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。

  (2)虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效,即基于JDK动态代理。

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

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

  (5)默认情况下,Spring会对unchecked异常进行事务回滚;如果是checked异常则不回滚。

  (6)通过元素的 "proxy-target-class" 属性值来控制是基于接口的还是基于类的代理被创建。如果 "proxy-target-class" 属值被设置为 "true",那么基于类的代理将起作用(CGLIB动态代理)。如果 "proxy-target-class" 属值被设置为 "false" 或者这个属性被省略,那么基于接口的代理将起作用(JDK动态代理)。

  java里面将派生于Error或者RuntimeException(比如空指针,1/0)的异常称为unchecked异常
  其他继承自java.lang.Exception得异常统称为Checked Exception,如IOException、TimeoutException等
  

  通俗一点:代码出现的空指针等异常,会被回滚,而文件读写,网络出问题,spring就没法回滚了。
  你写代码的时候有些IOException我们的编译器是能够检测到的,说以叫checked异常即必须使用try-catch包围的代码,你写代码的时候空指针等死检测不到的,所以叫unchecked异常。

二、使用实例 

1.方法不加注解抛异常,执行有异常抛出,z1表成功新增一条记录

public void testTransaction() {
db.update("insert into z1(c1) values('2')"); //执行成功
// 主动抛出异常 测试回滚
String str = null;
if (str.startsWith("111")) { // 空指针异常
db.update("insert into z2(c1,c2) values('2','3')"); //执行失败
}
}

2.方法加注解抛异常,并且方法是被其他类调用,执行有异常抛出,z1、z2表都没有新增记录,事务正常

@Transactional
public void testTransaction() {
db.update("insert into z1(c1) values('2')");
// 主动抛出异常 测试回滚
String str = null;
if (str.startsWith("111")) { // 空指针异常
db.update("insert into z2(c1,c2) values('2','3')");
}
}

3.方法加注解不抛异常,正常执行无异常,z1、z2表都新增了一条记录

@Transactional
public void testTransaction() {
db.update("insert into z1(c1) values('2')");
String str = "111";
if (str.startsWith("111")) {
db.update("insert into z2(c1,c2) values('2','3')");
}
}

4.方法加注解并调用该类其他方法并抛异常,执行有异常抛出,z1、z2表无新增记录

@Transactional
public void testTransaction() {
db.update("insert into z1(c1) values('2')");
testTransaction2();
}
public void testTransaction2() {
// 主动抛出异常 测试回滚
String str = null;
if (str.startsWith("111")) {
db.update("insert into z2(c1,c2) values('2','3')");
}
}

5.调用含注解的方法并抛异常,但是是被本类中的方法调用,执行有异常抛出,z1表新增记录、z2表无新增记录,事务失效

public void testTransaction() {
testTransaction3();
}
@Transactional
public void testTransaction3() {
db.update("insert into z1(c1) values('2')");
// 测试
String str = null;
if (str.startsWith("111")) {
db.update("insert into z1(c1) values('adff2')");
}
}

  在代理下(默认或当配置为proxy-target-class="true"),只有当前代理类的外部方法调用注解方法时代理才会被拦截。事实上,这意味着:一个目标对象的方法调用该目标对象的另外一个方法,即使被调用的方法已使用了@Transactional注解标记,事务也不会有效执行。

三、Spring事务传播模式

  所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:  

  1、REQUIRED(默认模式):业务方法需要在一个事务里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务。

  2、NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。

  3、REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。

  4、 MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。

  5、SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。

  6、NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。

  7、NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

四、解决Transactional注解不回滚

  (1)检查你方法是不是public的

  (2)你的异常类型是不是unchecked异常 ,如果想check异常也想回滚,则需要再注解上面写明异常类型:

  @Transactional(rollbackFor=Exception.class) 

  类似的还有norollbackFor,自定义不回滚的异常。

  (3)数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb支持事务,而myisam是不支持事务的。

  (4)是否开启了对注解的解析:

  <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

  (5)spring是否扫描到你这个包,如下是扫描到org.test下面的包:

  <context:component-scan base-package="org.test" ></context:component-scan>

  (6)检查是不是同一个类中的方法调用(如a方法调用同一个类中的b方法) 。

  (7)异常是不是被你catch住了。

五、Spring事务实现原理

1. 标签解析

关于事务的实现原理,我们首先讲解Spring是如何解析标签,并且封装相关bean的,后面我们会深入讲解Spring是如何封装数据库事务的。

根据上面的示例,我们发现,其主要有三个部分:DataSource,TransactionManager和tx:annotation-driven标签。这里前面两个部分主要是声明了两个bean,分别用于数据库连接的管理和事务的管理,而tx:annotation-driven才是Spring事务的驱动。根据(Spring自定义标签解析与实现)可以知道,这里tx:annotation-driven是一个自定义标签,我们根据其命名空间(www.springframework.org/schema/tx)在全局范围内搜索,可以找到其处理器指定文件spring.handlers,该文件内容如下:

  http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler

  这里也就是说tx:annotation-driven标签的解析在TxNamespaceHandler中,我们继续打开该文件可以看到起init()方法如下:

  public void init() {
this.registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
this.registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
this.registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
}

  可以看到,这里的annotation-driven是在AnnotationDrivenBeanDefinitionParser中进行处理的,其parse()方法就是解析标签,并且注册相关bean的方法,如下是该方法的实现:

public BeanDefinition parse(Element element, ParserContext parserContext) {
// 注册事务相关的监听器,如果某个方法标注了TransactionalEventListener注解,
// 那么该方法就是一个事务事件触发方法,即发生某种事务事件后,将会根据该注解的设置,回调指定类型的方法。常见的事务事件有:事务执行前和事务完成(包括报错后的完成)后的事件。
registerTransactionalEventListenerFactory(parserContext);
String mode = element.getAttribute("mode");
// 获取当前事务驱动程序的模式,如果使用了aspectj模式,则会注册一个AnnotationTransactionAspect类型的bean,用户可以以aspectj的方式使用该bean对事务进行更多的配置
if ("aspectj".equals(mode)) {
registerTransactionAspect(element, parserContext);
} else {
// 一般使用的是当前这种方式,这种方式将会在Spring中注册三个bean,分别是
// AnnotationTransactionAttributeSource,TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor,并通过Aop的方式实现事务
AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
}
return null;
}

  可以看到,对于事务的驱动,这里主要做了两件事:①注册事务相关的事件触发器,这些触发器由用户自行提供,在事务进行提交或事务完成时会触发相应的方法;②判断当前事务驱动的模式,有默认模式和aspectj模式,对于aspectj模式,Spring会注册一个AnnotationTransactionAspect类型的bean,用于用户使用更亲近于aspectj的方式进行事务处理;对于默认模式,这里主要是声明了三个bean,最终通过Aop的方式进行事务切入。下面我们看一下Spring是如何注册这三个bean的,如下是AopAutoProxyConfigurer.configureAutoProxyCreator的源码:

public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
// 这个方法主要是在当前BeanFactory中注册InfrastructureAdvisorAutoProxyCreator这个
// bean,这个bean继承了AbstractAdvisorAutoProxyCreator,也就是其实现原理与我们前面
// 讲解的Spring Aop的实现原理几乎一致
AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element); // 这里的txAdvisorBeanName就是我们最终要注册的bean,其类型就是下面注册的
// BeanFactoryTransactionAttributeSourceAdvisor,可以看到,其本质是一个
// Advisor类型的对象,因而Spring Aop会将其作为一个切面织入到指定的bean中
String txAdvisorBeanName = TransactionManagementConfigUtils
.TRANSACTION_ADVISOR_BEAN_NAME;
// 如果当前BeanFactory中已经存在了目标bean,则不进行注册
if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
Object eleSource = parserContext.extractSource(element);
// 注册AnnotationTransactionAttributeSource,这个bean的主要作用是封装
// @Transactional注解中声明的各个属性
RootBeanDefinition sourceDef = new RootBeanDefinition(
"org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
sourceDef.setSource(eleSource);
sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String sourceName = parserContext.getReaderContext()
.registerWithGeneratedName(sourceDef); // 注册TransactionInterceptor类型的bean,并且将上面的封装属性的bean设置为其一个属性。
// 这个bean本质上是一个Advice(可查看其继承结构),Spring Aop使用Advisor封装实现切面
// 逻辑织入所需的所有属性,但真正的切面逻辑却是保存在其Advice属性中的,也就是说这里的
// TransactionInterceptor才是真正封装了事务切面逻辑的bean
RootBeanDefinition interceptorDef =
new RootBeanDefinition(TransactionInterceptor.class);
interceptorDef.setSource(eleSource);
interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registerTransactionManager(element, interceptorDef);
interceptorDef.getPropertyValues().add("transactionAttributeSource",
new RuntimeBeanReference(sourceName));
String interceptorName = parserContext.getReaderContext()
.registerWithGeneratedName(interceptorDef); // 注册BeanFactoryTransactionAttributeSourceAdvisor类型的bean,这个bean实现了
// Advisor接口,实际上就是封装了当前需要织入的切面的所有所需的属性
RootBeanDefinition advisorDef =
new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
advisorDef.setSource(eleSource);
advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisorDef.getPropertyValues().add("transactionAttributeSource",
new RuntimeBeanReference(sourceName));
advisorDef.getPropertyValues().add("adviceBeanName", interceptorName);
if (element.hasAttribute("order")) {
advisorDef.getPropertyValues().add("order", element.getAttribute("order"));
}
parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef); // 将需要注册的bean封装到CompositeComponentDefinition中,并且进行注册
CompositeComponentDefinition compositeDef =
new CompositeComponentDefinition(element.getTagName(), eleSource);
compositeDef.addNestedComponent(
new BeanComponentDefinition(sourceDef, sourceName));
compositeDef.addNestedComponent(
new BeanComponentDefinition(interceptorDef, interceptorName));
compositeDef.addNestedComponent(
new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
parserContext.registerComponent(compositeDef);
}
}

  如此,Spring事务相关的标签即解析完成,这里主要是声明了一个BeanFactoryTransactionAttributeSourceAdvisor类型的bean到BeanFactory中,其实际为Advisor类型,这也是Spring事务能够通过Aop实现事务的根本原因。

2. 实现原理

关于Spring事务的实现原理,这里需要抓住的就是,其是使用Aop实现的,我们知道,Aop在进行解析的时候,最终会生成一个Adivsor对象,这个Advisor对象中封装了切面织入所需要的所有信息,其中就包括最重要的两个部分就是PointcutAdivce属性。这里Pointcut用于判断目标bean是否需要织入当前切面逻辑;Advice则封装了需要织入的切面逻辑。如下是这三个部分的简要关系图:

  

  同样的,对于Spring事务,其既然是使用Spring Aop实现的,那么也同样会有这三个成员。我们这里我们只看到了注册的Advisor和Advice(即BeanFactoryTransactionAttributeSourceAdvisor和TransactionInterceptor),那么Pointcut在哪里呢?这里我们查看BeanFactoryTransactionAttributeSourceAdvisor的源码可以发现,其内部声明了一个TransactionAttributeSourcePointcut类型的属性,并且直接在内部进行了实现,这就是我们需要找的Pointcut。这里这三个对象对应的关系如下:

  

这样,用于实现Spring事务的Advisor,Pointcut以及Advice都已经找到了。关于这三个类的具体作用,我们这里进行整体的上的讲解,后面我们将会深入其内部讲解其是如何进行bean的过滤以及事务逻辑的织入的。

  • BeanFactoryTransactionAttributeSourceAdvisor:封装了实现事务所需的所有属性,包括Pointcut,Advice,TransactionManager以及一些其他的在Transactional注解中声明的属性;
  • TransactionAttributeSourcePointcut:用于判断哪些bean需要织入当前的事务逻辑。这里可想而知,其判断的基本逻辑就是判断其方法或类声明上有没有使用@Transactional注解,如果使用了就是需要织入事务逻辑的bean;
  • TransactionInterceptor:这个bean本质上是一个Advice,其封装了当前需要织入目标bean的切面逻辑,也就是Spring事务是如果借助于数据库事务来实现对目标方法的环绕的。

4. 小结

本文首先通过一个简单的例子讲解了Spring事务是如何使用的,然后讲解了Spring事务进行标签解析的时候做了哪些工作,最后讲解了Spring事务是如何与Spring Aop进行一一对应的,并且是如何通过Spring Aop实现将事务逻辑织入目标bean的。

参考:

  1、Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别。  https://www.cnblogs.com/OnlyCT/p/7805187.html

  2、Spring事务用法示例与实现原理  https://www.cnblogs.com/linjunwei2017/p/9627922.html

  3、Spring -- <tx:annotation-driven>注解基于JDK动态代理和CGLIB动态代理的实现Spring注解管理事务(@Trasactional)的区别    https://www.cnblogs.com/OnlyCT/p/7805187.html

Spring事务实现分析的更多相关文章

  1. Spring事务原理分析-部分二

    Spring事务原理分析-部分二 说明:这是我在蚂蚁课堂学习了余老师Spring手写框架的课程的一些笔记,部分代码代码会用到余老师的课件代码.这不是广告,是我听了之后觉得很好. 课堂链接:Spring ...

  2. Spring事务原理分析-部分一

    Spring事务原理分析-部分一 什么事务 事务:逻辑上的一组操作,组成这组操作的各个单元,要么全都成功,要么全都失败. 事务基本特性 ⑴ 原子性(Atomicity) 原子性是指事务包含的所有操作要 ...

  3. Spring学习笔记(四)-- Spring事务全面分析

    通过本系列的文章对Spring的介绍,我们对Spring的使用和两个核心功能IOC.AOP已经有了初步的了解,结合我个人工作的情况,因为项目是金融系 统.那对事务的控制是不可缺少的.而且是很严格的控制 ...

  4. Spring事务原理分析--手写Spring事务

    一.基本概念和原理 1.Spring事务 基于AOP环绕通知和异常通知的 2.Spring事务分为编程式事务.声明事务.编程事务包括注解方式和扫包方式(xml) Spring事务底层使用编程事务(自己 ...

  5. Spring事务注解分析

    1.使用spring事务注解 2.手写事务注解 1).sql执行器 2).事务注解定义 3).AOP实现事务具体实现(同一个线程中使用同一个连接) 4).应用使用注解前 5).应用使用注解后

  6. Spring事务管理全面分析

    Spring 事务属性分析什么是事物  事务管理对于企业应用而言至关重要.它保证了用户的每一次操作都是可靠的,即便出现了异常的访问情况,也不至于破坏后台数据的完整性.就像银行的自助取款机,通常都能正常 ...

  7. Spring事务控制和回滚

    1在一个项目中ssh结构,spring2.5,事务控制采用的是tx拦截器的方式. 自己写了个 int a=1/0;异常抛出了,但是事务还是提交了,怎么搞都不行. 现将看到的一些事务控制总结下来: 事务 ...

  8. spring事务管理——编程式事务、声明式事务

    本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本教程假定您已经掌握了 ...

  9. Spring事务管理--多个ORM框架在使用时的情况分析

    公司的项目已经接近尾声了,总结一下项目中用到的技术,我发现项目中的有些东西还是挺模糊的,只是知道这么用就行了.并不清楚其中的原理.由于公司的项目比较老,是7年前的一个项目了,中间一直有人在维护,也是在 ...

随机推荐

  1. 经典问题----最短路径(Floyd弗洛伊德算法)(HDU2066)

    问题简介: 给定T条路,S个起点,D个终点,求最短的起点到终点的距离. 思路简介: 弗洛伊德算法即先以a作为中转点,再以a.b作为中转点,直到所有的点都做过中转点,求得所有点到其他点的最短路径,Flo ...

  2. Custom Default Node Colors and Shapes in Houdini 16.5

    A:before H16.5: 1.Create a file, named OPcustomize 2.Edit it like this: //Custom Default Shapes opde ...

  3. 引擎设计跟踪(九.14.3) deferred shading 准备

    目前做的一些准备工作 1.depth prepass for forward shading. 做depth prepass的原因是为了完善渲染流程, 虽然架构上支持多个pass, 但实际上从来没有测 ...

  4. 创建一个dynamics 365 CRM online plugin (八) - 使用Shared Variables 在plugins 之前传递data

    CRM 可以实现plugin之前的值传递. 我们可以使用SharedVariables 把值在plugin之间传递 实现plugins之间的传递非常简单,我们只需要用key value pair来配对 ...

  5. update_engine-DownloadAction(一)

    通过update_engine-整体结构(一),(二),(三)对update_engine整体的运行机制有了一定的认识之后.开始逐个分析重要的Action.先从DownloadAction开始分析. ...

  6. 【Leetcode】无重复字符的最长子串

    暴力解法,枚举所有子字符串组合 输入:长度[0,n]的字符串 耗时过长--- class Solution { public: int lengthOfLongestSubstring(string ...

  7. WMware 中CentOS系统Hadoop 分布式环境搭建(一)——Hadoop安装环境准备

    1.创建3台虚拟机并装好系统,这里使用64位CentOS. 2.Ping测试[确保两两能ping通]: [ping xxx.xxx.xxx.xxx] 3.安装SSH:[yum install ssh ...

  8. 解决WIN7第一次开机冷启动QQ未响应的办法

    为什么WIN7第一次开机冷启动QQ未响应?WIN10就没事? http://bbs.wuyou.net/forum.php?mod=viewthread&tid=409516&extr ...

  9. Ubuntu16.04安装Python3.6 和pip(python3 各版本切换)

    安装: sudo add-apt-repository ppa:jonathonf/python-3.6 sudo apt-get update sudo apt-get install python ...

  10. 基于STM8的ADC读取---STM8-第四章

    1. 综诉 想学会如何在STM8上使用ADC这个功能,我们先得了解单片机中ADC究竟是什么. ADC是模拟信号转成数值信号,单片机只能识别TTL电平,其实就是 1 或者 0 ,但是如果我们给它一个3. ...