Spring中事务用法示例及实现原理详解

前言

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。

关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性。对于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@Transactionalpublic 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@Transactionalpublic 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()方法如下:

@Overridepublic 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对象中封装了切面织入所需要的所有信息,其中就包括最重要的两个部分就是Pointcut和Adivce属性。这里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的。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Spring+SpringMVC配置事务管理无效原因及解决办法详解

    一般我们在Spring的配置文件application.xml中对Service层代码配置事务管理,可以对Service的方法进行AOP增强或事务处理如事务回滚,但是遇到一个问题,在Controller类中调用Service层方法,配置的事务管理会失效,查询相关资料发现原因.其实Spring和SpringMVC俩个容器为父子关系,Spring为父容器,而SpringMVC为子容器.也就是说application.xml中应该负责扫描除@Controller的注解如@Service,而Spring

  • Spring 事务事件监控及实现原理解析

    前面我们讲到了Spring在进行事务逻辑织入的时候,无论是事务开始,提交或者回滚,都会触发相应的事务事件.本文首先会使用实例进行讲解Spring事务事件是如何使用的,然后会讲解这种使用方式的实现原理. 1. 示例 对于事务事件,Spring提供了一个注解@TransactionEventListener,将这个注解标注在某个方法上,那么就将这个方法声明为了一个事务事件处理器,而具体的事件类型则是由TransactionalEventListener.phase属性进行定义的.如下是Transac

  • Spring中事务管理的四种方法(银行转账为例)

    前言 本文配套示例代码下载地址(完整可运行,含sql文件,下载后请修改数据库配置):点击这里下载 一.事务的作用 将若干的数据库操作作为一个整体控制,一起成功或一起失败. 原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生. 一致性:指事务前后数据的完整性必须保持一致. 隔离性:指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离. 持久性:指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,即时数据库发生故障也不应

  • Spring中的事务隔离级别的介绍

    spring事务: 什么是事务: 事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败. 事务特性(4种): 原子性 (atomicity):强调事务的不可分割. 一致性 (consistency):事务的执行的前后数据的完整性保持一致. 隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰 持久性(durability) :事务一旦结束,数据就持久到数据库 解决读问题: 设置事务隔离级别(5种) DEFAULT 这是一个PlatfromTran

  • Spring对事务管理的支持

    Spring对事务的支持有两种方式,一是自己编写事务,精确控制事务的边界,二是采用声明事务的方式,使用AOP来完成.无论哪种方式,我们都将使用一个Spring事务管理连接器连接特定平台的事务实现. Spring不直接管理事务,相反,它提供很多可供选择的事务管理器,将事务管理的责任委托给JTA(Java Transaction Interface)或相应的持久性机制所提供的某个特定平台事务实现. Spring提供的常用的事务管理器有DataSourceTransactionManager,Hibe

  • 在Spring中编写事务的介绍

    理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是要么都执行要么都不执行.如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元:如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元.所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是

  • 浅谈SpringBoot之事务处理机制

    一.Spring的事务机制 所有的数据访问技术都有事务处理机制,这些技术提供了API用来开启事务.提交事务来完成数据操作,或者在发生错误的时候回滚数据. 而Spring的事务机制是用统一的机制来处理不同数据访问技术的事务处理.Spring的事务机制提供了一个PlatformTransactionManager接口,不同的数据访问技术的事务使用不同的接口实现: 在程序中定义事务管理器的代码如下: @Bean public PlatformTransactionManager transaction

  • SpringDataMongoDB多文档事务的实现

    一.安装MongoDB4.0.3(××) 1.1.官方安装文档 https://docs.mongodb.com/manual/tutorial/install-mongodb-on-red-hat/ 1.2.tar.gz包下载地址 https://www.mongodb.com/download-center/community?jmp=docs 1.3.复制集官方配置 https://docs.mongodb.com/manual/administration/replica-set-mem

  • Spring中事务传播行为的介绍

    传播行为定义关于客户端和被调用方法的事务边界.Spring定义了7种截然不同的传播行为. 1,PROPAGATION_MANDATORY,表示该方法必须运行在一个事务中.如果当前没有事务正在发生,则抛出异常. 2,PROPAGATION_NESTED,表示如果当前有一个事务正在运行当中,则该方法应该运行在一个嵌套事务中.被嵌套的事务可以独立于封装事务进行提交或回滚.如果封装事务不存在,则行为就像PROPAGATION_REQUIRED一样. 3,PROPAGATION_NEVER,表示当前方法不

  • spring事务异常回滚实例解析

    最近遇到了事务不回滚的情况,我还考虑说JPA的事务有bug?我想多了....... 为了打印清楚日志,很多方法我都加tyrcatch,在catch中打印日志.但是这边情况来了,当这个方法异常时候日志是打印了,但是加的事务却没有回滚. 例: 类似这样的方法不会回滚(一个方法出错,另一个方法不会回滚): if(userSave){ try { userDao.save(user); userCapabilityQuotaDao.save(capabilityQuota); } catch (Exce

随机推荐