Spring事务你可能不知道的事儿

戳蓝字“CSDN云计算”关注我们哦!


  • 1. 使用示例

  • 2. 标签解析

  • 3. 实现原理

  • 4. 小结


关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性。对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比较优雅的方式对其进行封装支持。本文首先会通过一个简单的示例来讲解Spring事务是如何使用的,然后会讲解Spring是如何解析xml中的标签,并对事务进行支持的。

1. 使用示例

关于事务最简单的示例,就是其一致性,比如在整个事务执行过程中,如果任何一个位置报错了,那么都会导致事务回滚,回滚之后数据的状态将和事务执行之前完全一致。这里我们以用户数据为例,在插入用户数据的时候,如果程序报错了,那么插入的动作就会回滚。如下是用户的实体:

public class User {
  private long id;
  private String name;
  private int age;

  // getter, setter...
}

如下是模拟插入用户数据的业务代码:

public interface UserService {
  void insert(User user);
}

@Service
@Transactional
public class UserServiceImpl implements UserService {
  @Autowired
  private JdbcTemplate jdbcTemplate;

  @Override
  public void insert(User user) {
    jdbcTemplate.update("insert into user (name, age) value (?, ?)",
        user.getName(), user.getAge());
  }
}

在进行事务支持时,Spring只需要使用者在需要事务支持的bean上使用@Transactional注解即可,如果需要修改事务的隔离级别和传播特性的属性,则使用该注解中的属性进行指定。这里默认的隔离级别与各个数据库一致,比如MySQL是Repeatable Read,而传播特性默认则为Propagation.REQUIRED,即只需要当前操作具有事务即可。如下是xml文件的配置:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="url" value="jdbc:mysql://localhost/test?useUnicode=true"/>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="username" value="****"/>
    <property name="password" value="******"/>
</bean>

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<context:component-scan base-package="com.transaction"/>
<tx:annotation-driven/>

上述数据库配置用户按照各自的设置进行配置即可。可以看到,这里对于数据库的配置,主要包括四个方面:

  • DataSource配置:设置当前应用所需要连接的数据库,包括链接,用户名,密码等;

  • JdbcTemplate声明:封装了客户端调用数据库的方式,用户可以使用其他的方式,比如JpaRepository,Mybatis等等;

  • TransactionManager配置:指定了事务的管理方式,这里使用的是DataSourceTransactionManager,对于不同的链接方式,也可以进行不同的配置,比如对于JpaRepository使用JpaTransactionManager,对于Hibernate,使用HibernateTransactionManager;

  • tx:annotation-driven:主要用于事务驱动,其会通过AOP的方式声明一个为事务支持的Advisor,通过该Advisor和事务的相关配置进行事务相关操作。

按照上述配置,我们的事务功能即配置完成,如下是我们的驱动类程序:

public class TransactionApp {
  @Test
  public void testTransaction() {
    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserService userService = ac.getBean(UserService.class);
    User user = getUser();
    userService.insert(user);
  }

  private User getUser() {
    User user = new User();
    user.setName("Mary");
    user.setAge(27);
    return user;
  }
}

运行上述程序之后,可以看到数据库中成功新增了一条数据。这里如果我们将业务代码的插入语句之后手动抛出一个异常,那么,理论上插入语句是会回滚的。如下是修改后的service代码:

@Service
@Transactional
public class UserServiceImpl implements UserService {
  @Autowired
  private JdbcTemplate jdbcTemplate;

  @Override
  public void insert(User user) {
    jdbcTemplate.update("insert into user (name, age) value (?, ?)",
        user.getName(), user.getAge());
    throw new RuntimeException();
  }
}

这里我们手动抛出了一个RuntimeException,再次运行上述程序之后我们发现数据库中是没有新增的数据的,这说明我们的事务在程序出错后是能够保证数据一致性的。

2. 标签解析

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

根据上面的示例,我们发现,其主要有三个部分:DataSource,TransactionManager和tx:annotation-driven标签。这里前面两个部分主要是声明了两个bean,分别用于数据库连接的管理和事务的管理,而tx:annotation-driven才是Spring事务的驱动。根据本人前面对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()方法如下:

@Override
public void init() {
    registerBeanDefinitionParser("advice"new TxAdviceBeanDefinitionParser());
    registerBeanDefinitionParser("annotation-driven",
        new AnnotationDrivenBeanDefinitionParser());
    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实现事务的根本原因。

3. 实现原理

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

640?
Advisor

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

640?
Transaction

这样,用于实现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.微信群:

添加小编微信:color_ld,备注“进群+姓名+公司职位”即可,加入【云计算学习交流群】,和志同道合的朋友们共同打卡学习!


2.征稿:

投稿邮箱:liudan@csdn.net;微信号:color_ld。请备注投稿+姓名+公司职位。


推荐阅读

  • Istio,下一个Kubernetes?

  • 云计算的“傲慢”与“偏见”

  • 大数据时代,谁的眼神锁定你?

  • 别吐槽了!面试要求徒手写代码?你与顶级程序员的差别就在这

  • 算法工程师独得恩宠 四面楚歌的Android工程师该何去何从?

  • 腾讯将创办腾讯云启商学院,马化腾任荣誉院长

  • 扎堆出海的抖音、今日头条、UC 头条们后来怎么样了?

  • 创业者老板被程序员「割」了韭菜?

  • 中国云计算的十年江湖


640?wx_fmt=jpeg2018 中国大数据技术大会将于 12 月 6 -8 日在新云南皇冠假日酒店举行。汇聚超百位国内外实力讲师从学界翘楚到行业一线大拿,管晓宏:中国科学院院士、张宏江:源码资本投资合伙人、张晓东:美国俄亥俄州立大学Robert M. Critchfield讲席教授、陈性元 :北京信息科学技术研究院副院长、周靖人:阿里巴巴集团副总裁、李浩源:Alluxio公司创始人&CEO等重磅嘉宾,全方位立体解读大数据时代的技术进程,为众技术爱好者奉上一场优质干货盛宴。


640?wx_fmt=jpeg

↓↓↓  点击【阅读原文】查看「CSDN云计算」往期精彩内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/525189.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BugkuCTF-PWN题canary超详细讲解

知识点 小端序说明&#xff0c;数据在内存里是如何存储的&#xff1f;下表里数据都为16进制 解题流程 题目Hint:更新 LibcSeacher 的 libc-database checksec查看保护机制 存在Canary和NX。 0x2840 0x1016 0x2941 0x300768 0x2C44 buf长度为48&#xff0c;而read读取长度…

Cloud一分钟 |乐视系近亿元资产被查封;快手推出新社区产品,取名“蹦迪”;高通:已终止收购恩智浦的交易...

戳蓝字“CSDN云计算”关注我们哦&#xff01;Hello&#xff0c;everyone&#xff1a;12月4日早&#xff0c;星期二CSDN一分钟新闻时间&#xff1a;乐视系近亿元资产被查封 裁定查封、扣押或冻结三家乐视系公司名下价值9002.2万元财产。 …

BugkuCTF-Crypto题散乱的密文

描述: lf5{ag024c483549d7fd1} 一张纸条上凌乱的写着2 1 6 5 3 4 知识点&#xff1a;列位移密码&#xff0c;密钥为216534 打开密码机器下载版.htm 点击解密得到flag

倒计时 2 天,2018 中国大数据技术大会(BDTC)报名通道即将关闭(附参会提醒)...

戳蓝字“CSDN云计算”关注我们哦&#xff01;2018 年12 月 6-8 日&#xff0c;由中国计算机学会主办&#xff0c;CCF大数据专家委员会承办&#xff0c;CSDN、中科天玑数据科技股份有限公司协办的 2018 中国大数据技术大会&#xff08;BDTC&#xff09;&#xff0c;携主题“大数…

BugkuCTF-PWN题pwn5-overflow2超详细讲解

知识点 setvbuf: setvbuf函数的功能&#xff1a; 如果你的内存足够大&#xff0c;可以把文件IO的BUF设置大一些&#xff0c;这样每次你用 fopen/fread/fwrite/fscanf/fprintf语句的时候&#xff0c;都会在内存里操作&#xff0c;减少内存到磁盘IO读写的操作次数&#xff0c;提…

下载 / 安装 Visual Studio - C语言零基础入门教程

目录 一.下载 Visual Studio 1.Visual Studio 下载地址2.Visual Studio 分类3.Visual Studio 下载 二.安装 Visual Studio 1.安装虚拟光驱2.安装 VS 三.猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >> C 语言基础入门 天下武功&#xff0c;唯快不破&#xff0c;虽…

想让马云成为你的老大?揭秘阿里面试情景

戳蓝字“CSDN云计算”关注我们哦&#xff01;前言最近评论的小伙伴总是会问&#xff0c;如何面试阿里Java技术岗&#xff0c;需要什么条件&#xff0c;做哪些准备&#xff1b;小编就这些问题找到了阿里技术团队中在一线真正带Java开发团队并直接参与技术面试的专家&#xff0c;…

BugkuCTF-PWN题pwn6-printf超详细讲解(未提供Libc版本)

前言 此题是我根据某大佬wp(从Libc官网下载的Libc)解出的&#xff0c;我在他的exp脚本基础上进行修改通过LibcSearcher搜索可利用的Libc&#xff0c;因为这道题在Bugku未提供Libc版本 有些部分很难理解&#xff0c;如果有大佬知道的&#xff0c;还请不吝赐教 还是先把解题流…

安装 Visual Studio 插件 Visual Assist - C语言零基础入门教程

目录 一.下载 Visual Assist 插件二.安装 Visual Assist 插件 1.运行 VA_X_Setup2270_0.exe2.激活插件3.重启 Visual Studio 完成安装 三.猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >> C 语言基础入门 前一篇文章中我们已经把 Visual Studio 2015 安装 好了&…

名企程序员被裁实录:早上还在改 Bug,晚上就成下岗工

戳蓝字“CSDN云计算”关注我们哦&#xff01;尽管最近新闻铺天盖地的“寒冬说”&#xff0c;由于我多年身处在稳定的大公司里&#xff0c;并没有太多的危机感。昨天大伙一起讨论年会表演什么节目&#xff0c;你演宁采臣&#xff0c;他男扮女装演小倩&#xff0c;大胖就演宁采臣…

BugkuCTF-PWN题pwn7-repeater详细讲解多解法

知识点 解题流程 方法一 查看文件类型&#xff1a; 32位文件 查看保护机制 只开启了NX 32位IDA打开 伪码&#xff1a; 0x70112 0x64100 发现该题目为典型的格式字符串漏洞。 解题思路 此题的大概思路如下&#xff1a; 1、找到libc_start_main在栈内的偏移&#xff0c;…

HR怒甩程序员男友:不加班没上进没前途,网友:惹不起

戳蓝字“CSDN云计算”关注我们哦&#xff01;对于程序员来说&#xff0c;加班就是家常便饭&#xff0c;但也有特例&#xff0c;最近就有为女 HR 在某论坛吐槽&#xff1a;最近和一个程序员谈恋爱&#xff0c;只持续三天就把程序员甩了&#xff0c;原因是男朋友每天六点下班&…

BugkuCTF-Crypto题rsa

解题流程 n&#xff0c;e已经给出&#xff0c;可以看出e特别大&#xff0c;在e特别大的情况下&#xff0c;可以使用wiener attack的方法进行破解&#xff0c;正好工具RsaCtfTool集成了wiener attack的方法&#xff0c;所以可以直接使用RsaCtfTool计算私钥。 典型的rsa… 密钥的…

Visual Studio 2008 完全卸载 - C语言零基础入门教程

目录 一.VS2008 卸载方式一 (不推荐)二.VS2008 卸载方式一 ( 推荐)三.猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >> C 语言基础入门 Visual Studio 已经更新到 2019&#xff0c;可能部分公司为了保证代码的兼容性&#xff0c;还是在使用比较老的 2005 或者 2008…

相亲网站比自己优秀的男人太多?单身程序员惊现神操作!

戳蓝字“CSDN云计算”关注我们哦&#xff01;话说&#xff0c;今年还有不到一个月就要过去了&#xff0c;作为一个单身狗的小编&#xff0c;看着大街上一个个成双成对的情侣&#xff0c;不由感到悲从心来&#xff0c;只能妄图从新闻中寻找一丝安慰。结果你别说&#xff0c;我最…

BugkuCTF-Crypto题给你私钥吧

方法一 用 RsaCtfTool这个脚本工具。。。然后python RsaCtfTool.py --publickey pubkey.pem --uncipherfile flag.enc直接用公钥进行攻击解密 方法二 思路&#xff1a;高位攻击还原pq&#xff0c;然后生成密钥进行解密 分三步&#xff1a; 3.第三步通过私钥文件OAEP解密…

Visual Studio 2013 / 2015 完全卸载 - C语言零基础入门教程

目录 一.查找 vs_community.exe 文件二.以管理身份打开 cmd 窗口三.使用微软官方的卸载工具来清理残余数据 1.下载清理工具2.以管理员身份执行 setup.forceduninstall.exe&#xff1b;3.按 Y 进行卸载&#xff1b; 四猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >>…

漫画:如何实现大整数相加?

戳蓝字“CSDN云计算”关注我们哦&#xff01;————— 第二天 —————————————————在程序中列出的 “竖式” 究竟是什么样子呢&#xff1f;我们以 426709752318 95481253129 为例&#xff0c;来看看大整数相加的详细步骤&#xff1a;第一步&#xff0c;把整…

BugkuCTF-Crypto题杰斐逊

打开下载文件 这是一类加密方式&#xff0c;叫做轮转密码或者叫比尔密码&#xff0c;解密的方式就是先通过密钥调整字符串的行号&#xff0c;然后通过密文调整一行字符串的顺序&#xff0c;例如&#xff0c;密文的第一个字母是H&#xff0c; 第一行字符串是 则&#xff0c;将…

设置 Visual Studio 字体/背景/行号 - C语言零基础入门教程

目录 一.设置 Visual Studio 颜色主题二.显示 Visual Studio 行号三.设置 Visual Studio 字体大小/风格四.猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >> C 语言基础入门 Visual Studio 默认有三个主题&#xff0c;在我们刚开始安装 Visual Studio 的时候就能看到…