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

1. 使用示例

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


  1. public class User {
  2. private long id;
  3. private String name;
  4. private int age;
  5. // getter, setter...
  6. }

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


  1. public interface UserService {
  2. void insert(User user);
  3. }
  4. @Service
  5. @Transactional
  6. public class UserServiceImpl implements UserService {
  7. @Autowired
  8. private JdbcTemplate jdbcTemplate;
  9. @Override
  10. public void insert(User user) {
  11. jdbcTemplate.update("insert into user (name, age) value (?, ?)",
  12. user.getName(), user.getAge());
  13. }
  14. }

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


  1. <code class="language-xml">&lt;bean id=&quot;dataSource&quot; class=&quot;org.apache.commons.dbcp.BasicDataSource&quot; destroy-method=&quot;close&quot;&gt;
  2. &lt;property name=&quot;url&quot; value=&quot;jdbc:mysql://localhost/test?useUnicode=true&quot;/&gt;
  3. &lt;property name=&quot;driverClassName&quot; value=&quot;com.mysql.jdbc.Driver&quot;/&gt;
  4. &lt;property name=&quot;username&quot; value=&quot;****&quot;/&gt;
  5. &lt;property name=&quot;password&quot; value=&quot;******&quot;/&gt;
  6. &lt;/bean&gt;
  7. &lt;bean id=&quot;jdbcTemplate&quot; class=&quot;org.springframework.jdbc.core.JdbcTemplate&quot;&gt;
  8. &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt;
  9. &lt;/bean&gt;
  10. &lt;bean id=&quot;transactionManager&quot; class=&quot;org.springframework.jdbc.datasource.DataSourceTransactionManager&quot;&gt;
  11. &lt;property name=&quot;dataSource&quot; ref=&quot;dataSource&quot;/&gt;
  12. &lt;/bean&gt;
  13. &lt;context:component-scan base-package=&quot;com.transaction&quot;/&gt;
  14. &lt;tx:annotation-driven/&gt;

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

  • DataSource配置:设置当前应用所需要连接的数据库,包括链接,用户名,密码等;
  • JdbcTemplate声明:封装了客户端调用数据库的方式,用户可以使用其他的方式,比如JpaRepository,Mybatis等等;
  • TransactionManager配置:指定了事务的管理方式,这里使用的是DataSourceTransactionManager,对于不同的链接方式,也可以进行不同的配置,比如对于JpaRepository使用JpaTransactionManager,对于Hibernate,使用HibernateTransactionManager;
  • tx:annotation-driven:主要用于事务驱动,其会通过AOP的方式声明一个为事务支持的Advisor,通过该Advisor和事务的相关配置进行事务相关操作。

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


  1. public class TransactionApp {
  2. @Test
  3. public void testTransaction() {
  4. ApplicationContext ac = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;);
  5. UserService userService = ac.getBean(UserService.class);
  6. User user = getUser();
  7. userService.insert(user);
  8. }
  9. private User getUser() {
  10. User user = new User();
  11. user.setName(&quot;Mary&quot;);
  12. user.setAge(27);
  13. return user;
  14. }
  15. }

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


  1. @Service
  2. @Transactional
  3. public class UserServiceImpl implements UserService {
  4. @Autowired
  5. private JdbcTemplate jdbcTemplate;
  6. @Override
  7. public void insert(User user) {
  8. jdbcTemplate.update(&quot;insert into user (name, age) value (?, ?)&quot;,
  9. user.getName(), user.getAge());
  10. throw new RuntimeException();
  11. }
  12. }

这里我们手动抛出了一个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,该文件内容如下:


  1. <code class="language-xml">http\://www.springframework.org/schema/tx=org.springframework.transaction.config.TxNamespaceHandler

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


  1. @Override
  2. public void init() {
  3. registerBeanDefinitionParser(&quot;advice&quot;, new TxAdviceBeanDefinitionParser());
  4. registerBeanDefinitionParser(&quot;annotation-driven&quot;,
  5. new AnnotationDrivenBeanDefinitionParser());
  6. registerBeanDefinitionParser(&quot;jta-transaction-manager&quot;,
  7. new JtaTransactionManagerBeanDefinitionParser());
  8. }

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


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

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


  1. public static void configureAutoProxyCreator(Element element,
  2. ParserContext parserContext) {
  3. // 这个方法主要是在当前BeanFactory中注册InfrastructureAdvisorAutoProxyCreator这个
  4. // bean,这个bean继承了AbstractAdvisorAutoProxyCreator,也就是其实现原理与我们前面
  5. // 讲解的Spring Aop的实现原理几乎一致
  6. AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);
  7. // 这里的txAdvisorBeanName就是我们最终要注册的bean,其类型就是下面注册的
  8. // BeanFactoryTransactionAttributeSourceAdvisor,可以看到,其本质是一个
  9. // Advisor类型的对象,因而Spring Aop会将其作为一个切面织入到指定的bean中
  10. String txAdvisorBeanName = TransactionManagementConfigUtils
  11. .TRANSACTION_ADVISOR_BEAN_NAME;
  12. // 如果当前BeanFactory中已经存在了目标bean,则不进行注册
  13. if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
  14. Object eleSource = parserContext.extractSource(element);
  15. // 注册AnnotationTransactionAttributeSource,这个bean的主要作用是封装
  16. // @Transactional注解中声明的各个属性
  17. RootBeanDefinition sourceDef = new RootBeanDefinition(
  18. &quot;org.springframework.transaction.annotation.AnnotationTransactionAttributeSource&quot;);
  19. sourceDef.setSource(eleSource);
  20. sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  21. String sourceName = parserContext.getReaderContext()
  22. .registerWithGeneratedName(sourceDef);
  23. // 注册TransactionInterceptor类型的bean,并且将上面的封装属性的bean设置为其一个属性。
  24. // 这个bean本质上是一个Advice(可查看其继承结构),Spring Aop使用Advisor封装实现切面
  25. // 逻辑织入所需的所有属性,但真正的切面逻辑却是保存在其Advice属性中的,也就是说这里的
  26. // TransactionInterceptor才是真正封装了事务切面逻辑的bean
  27. RootBeanDefinition interceptorDef =
  28. new RootBeanDefinition(TransactionInterceptor.class);
  29. interceptorDef.setSource(eleSource);
  30. interceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  31. registerTransactionManager(element, interceptorDef);
  32. interceptorDef.getPropertyValues().add(&quot;transactionAttributeSource&quot;,
  33. new RuntimeBeanReference(sourceName));
  34. String interceptorName = parserContext.getReaderContext()
  35. .registerWithGeneratedName(interceptorDef);
  36. // 注册BeanFactoryTransactionAttributeSourceAdvisor类型的bean,这个bean实现了
  37. // Advisor接口,实际上就是封装了当前需要织入的切面的所有所需的属性
  38. RootBeanDefinition advisorDef =
  39. new RootBeanDefinition(BeanFactoryTransactionAttributeSourceAdvisor.class);
  40. advisorDef.setSource(eleSource);
  41. advisorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  42. advisorDef.getPropertyValues().add(&quot;transactionAttributeSource&quot;,
  43. new RuntimeBeanReference(sourceName));
  44. advisorDef.getPropertyValues().add(&quot;adviceBeanName&quot;, interceptorName);
  45. if (element.hasAttribute(&quot;order&quot;)) {
  46. advisorDef.getPropertyValues().add(&quot;order&quot;, element.getAttribute(&quot;order&quot;));
  47. }
  48. parserContext.getRegistry().registerBeanDefinition(txAdvisorBeanName, advisorDef);
  49. // 将需要注册的bean封装到CompositeComponentDefinition中,并且进行注册
  50. CompositeComponentDefinition compositeDef =
  51. new CompositeComponentDefinition(element.getTagName(), eleSource);
  52. compositeDef.addNestedComponent(
  53. new BeanComponentDefinition(sourceDef, sourceName));
  54. compositeDef.addNestedComponent(
  55. new BeanComponentDefinition(interceptorDef, interceptorName));
  56. compositeDef.addNestedComponent(
  57. new BeanComponentDefinition(advisorDef, txAdvisorBeanName));
  58. parserContext.registerComponent(compositeDef);
  59. }
  60. }

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

3. 实现原理

关于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的。

原文链接:https://my.oschina.net/zhangxufeng/blog/1935556

Spring事务用法示例与实现原理的更多相关文章

  1. Spring事务管理机制的实现原理-动态代理

    之前在做项目中遇到spring无法进行事务代理问题,最后发现是因为没有写接口,原因当时明白了,看到这篇文章写的清楚些,转过来 我们先来分析一下Spring事务管理机制的实现原理.由于Spring内置A ...

  2. spring事务-说说Propagation及其实现原理

    前言 spring目前已是java开发的一个事实标准,这得益于它的便利.功能齐全.容易上手等特性.在开发过程当中,操作DB是非常常见的操作,而涉及到db,就会涉及到事务.事务在平时的开发过程当中,就算 ...

  3. 手写spring事务框架, 揭秘AOP实现原理。

    AOP面向切面编程:主要是通过切面类来提高代码的复用,降低业务代码的耦合性,从而提高开发效率.主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等. AOP实现原理:aop是通过cgli ...

  4. Spring事务讲解示例

    Spring 事务Transaction1.事务的属性1.1 事务隔离IsolationLevel1.2 事务传播PropagationBehavior1.3 事务超时Timeout1.4 只读状态R ...

  5. Spring事务讲解示例(转)

    Spring 事务Transaction1.事务的属性1.1 事务隔离IsolationLevel1.2 事务传播PropagationBehavior1.3 事务超时Timeout1.4 只读状态R ...

  6. Spring事务银行转账示例

    https://www.imooc.com/video/9331 声明式事务 @Transactiona() 编程式事务 非模板式(不使用TransactionTemplate) http://cai ...

  7. 2018.11.12 Spring事务的实现和原理

    Spring事务用法示例与实现原理 关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性.一致性.隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而 ...

  8. Spring事务事件监控

    前面我们讲到了Spring在进行事务逻辑织入的时候,无论是事务开始,提交或者回滚,都会触发相应的事务事件.本文首先会使用实例进行讲解Spring事务事件是如何使用的,然后会讲解这种使用方式的实现原理. ...

  9. Spring事务实现分析

    一.Spring声明式事务用法 1.在spring配置文件中配置事务管理器 <bean id="baseDataSource" class="com.alibaba ...

随机推荐

  1. 【IIS】模块 DLL C:\Windows\System32\inetsrv\authcert.dll 未能加载。返回的数据为错误信息。

    解决方案,check  IIS --Client Certificate Mapping Authentication installed?

  2. ListView.setDivider,自定义的Devider

    ListView lv = getListView(); ColorDrawable sage = new ColorDrawable(this.getResources().getColor(R.c ...

  3. vue ios自带拼音全键输入法模糊查询兼容性问题

    ios的自带拼音全键会在输入框中输入拼音,直接在输入框用@keyup="autoInput()"的话,在监听输入事件的时候安卓显示正常, ios就会出现输入显示数据不灵敏 解决办法 ...

  4. 遍历Map集合四中方法

    public static void main(String[] args) { Map<String, String> map = new HashMap<String, Stri ...

  5. ZOJ 3607 Lazier Salesgirl (枚举)

    Lazier Salesgirl Time Limit: 2 Seconds Memory Limit: 65536 KB Kochiya Sanae is a lazy girl who makes ...

  6. 在nginx启动后,如果我们要操作nginx,要怎么做呢 别增加无谓的上下文切换 异步非阻塞的方式来处理请求 worker的个数为cpu的核数 红黑树

    nginx平台初探(100%) — Nginx开发从入门到精通 http://ten 众所周知,nginx性能高,而nginx的高性能与其架构是分不开的.那么nginx究竟是怎么样的呢?这一节我们先来 ...

  7. Windows中压缩版的MySQL的安装、配置

    本次笔记是根据mysql-8.0.13-winx64版本编写: 1.将下载的压缩包解压到自己想放的目录 2.右键计算机 -> 属性 -> 高级系统设置 -> 环境变量 -> 系 ...

  8. zipline整体架构

    在这里可以看出,zipline由下面几个主要的部分构成 名称 说明 TradingAlgorithm 量化策略的抽象,既可以通过初始化传入构造上参数的方式,也可以通过继承的方式构造,其中zipline ...

  9. MySQL和时间戳有关的函数

    1. MySQL 获得当前时间戳函数:current_timestamp, current_timestamp()mysql> select current_timestamp, current ...

  10. spring 自定义事物同步器(一): TransactionSynchronizationManager 解析

    一..JPA 获取 Hibernate的session try { session = entityManager.unwrap(Session.class); } catch (Exception ...