分析Spring事务管理原理及应用

news2024/10/6 19:27:02

目录

一、Spring事务管理介绍

(一)基本理论

(二)实际工作中的举例

(三)简单应用举例

二、Spring事务配置介绍

(一)Spring事务属性介绍

 传播属性(传播行为)可选值说明

(二)声明式事务配置示例

基于XML的配置

基于注解的配置

三、分析Spring事务实现原理

(一)整体流程梳理

 (二)核心实现分析

annotation-driven 解析

代理类创建分析

寻找合适通知器的时序图展示

InfrastructureAdvisorAutoProxyCreator处理器注册与实例化展示

为使用事务的目标类生成代理的时序图展示

事务处理分析

 四、总结

参考文献


一、Spring事务管理介绍

(一)基本理论

在软件开发中,事务管理是一种处理数据库操作的方式,它可以确保一组数据库操作要么全部成功执行,要么全部失败回滚,从而保持数据的一致性和完整性

Spring框架是一个流行的Java企业级应用程序开发框架,提供了强大的事务管理功能,可以轻松地在Spring应用程序中管理数据库事务。Spring事务管理通过提供一种声明性事务管理的方式,将事务管理从业务逻辑中解耦,并通过使用AOP(面向切面编程)技术实现事务管理。Spring框架支持多种事务管理策略,包括本地事务和分布式事务。

Spring事务管理的一些核心概念和功能包括:

  1. 事务管理器(Transaction Manager):负责管理数据库事务的对象。Spring支持多种事务管理器,如JDBC事务管理器、Hibernate事务管理器、JTA事务管理器等。
  2. 事务传播行为(Transaction Propagation):定义了在多个事务方法相互调用时,事务如何传播的规则。例如,一个事务方法A调用了另一个事务方法B,事务传播行为定义了B方法是加入A方法的事务,还是创建一个新的事务。
  3. 事务隔离级别(Transaction Isolation Level):定义了事务对数据库的隔离程度。不同的隔离级别提供了不同的并发控制策略,例如读未提交、读已提交、可重复读和串行化。
  4. 事务回滚(Transaction Rollback):当事务操作失败时,事务管理器可以自动回滚所有已执行的数据库操作,从而将数据库状态恢复到事务开始前的状态。
  5. 声明式事务管理(Declarative Transaction Management):通过在方法或类级别上使用Spring的事务管理注解,如@Transactional,可以将事务管理规则与业务逻辑解耦。这样,开发人员可以在代码中专注于业务逻辑,而不需要显式地编写事务管理代码。
  6. 编程式事务管理(Programmatic Transaction Management):可以使用编程方式通过编写事务管理代码来管理事务,例如使用Spring的TransactionTemplate API。

Spring事务管理可以应用于多种数据访问技术,如JDBC、Hibernate、JPA等,并且可以与其他Spring特性,如Spring的AOP和Spring的IoC容器一起使用,从而提供了一个强大的事务管理解决方案,使开发人员能够轻松地实现事务管理,并确保数据的一致性和完整性。

(二)实际工作中的举例

在实际工作中,Spring事务管理广泛应用于各种Java企业级应用程序中,包括Web应用、后端服务、批处理作业等。以下是一些使用Spring事务管理的实际应用举例:

  1. 电商应用:在一个电商应用中,订单管理涉及到对多个数据库表的增、删、改等操作,例如创建订单、更新库存、扣减账户余额等。使用Spring事务管理,可以确保这些数据库操作要么全部成功提交,要么全部回滚,从而保持订单和库存、账户之间的一致性。
  2. 银行系统:在一个银行系统中,涉及到对账户的转账、存款、取款等操作,这些操作必须保证在一个事务中进行,以确保资金的正确处理。使用Spring事务管理,可以将这些操作封装在一个事务中,从而保持账户操作的一致性和完整性。
  3. 社交媒体应用:在一个社交媒体应用中,用户可能同时进行多个操作,例如发布帖子、评论、点赞等。使用Spring事务管理,可以将这些操作组织成一个事务,从而保持数据的一致性,例如在发布帖子时,同时插入帖子信息和更新用户的动态信息。
  4. 后端服务:在后端服务中,可能会涉及到多个数据库操作,例如从多个数据源中读取数据、更新多个数据库表等。使用Spring事务管理,可以将这些操作组织成一个事务,从而保证数据操作的一致性。
  5. 批处理作业:在批处理作业中,可能需要对大量数据进行处理,包括从文件读取数据、处理数据、将结果写入数据库等。使用Spring事务管理,可以将这些操作封装在一个事务中,从而确保数据处理的一致性和完整性。

这些只是一些应用Spring事务管理的实际举例,实际上,在许多复杂的应用中,使用Spring事务管理可以有效地管理数据库事务,确保数据的一致性和完整性,并提供良好的容错能力和可靠性。

(三)简单应用举例

当使用Spring事务管理时,通常需要通过配置事务管理器、事务切面等来实现。下面是一个简单的Java代码示例,演示了如何在Spring中配置和使用事务管理。

假设有一个简单的订单管理系统,包括订单Service和订单DAO两个类,其中订单Service负责处理订单的业务逻辑,订单DAO负责与数据库进行交互。在订单Service中,我们希望对创建订单和更新订单状态两个操作进行事务管理。

首先,在Spring的配置文件中配置事务管理器和事务切面,例如使用Spring的声明式事务管理:

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

<!-- 配置事务切面 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- 配置需要进行事务管理的方法 -->
        <tx:method name="createOrder" propagation="REQUIRED" />
        <tx:method name="updateOrderStatus" propagation="REQUIRED" />
    </tx:attributes>
</tx:advice>

<!-- 配置AOP代理 -->
<aop:config>
    <aop:pointcut id="txPointcut" expression="execution(* com.example.order.service..*.*(..))" />
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" />
</aop:config>

接下来,在订单Service中使用@Transactional注解标记需要进行事务管理的方法,例如:

@Service
public class OrderService {

    @Autowired
    private OrderDao orderDao;

    @Transactional
    public void createOrder(Order order) {
        // 创建订单的业务逻辑
        orderDao.createOrder(order);
    }

    @Transactional
    public void updateOrderStatus(Long orderId, String status) {
        // 更新订单状态的业务逻辑
        orderDao.updateOrderStatus(orderId, status);
    }
}

在上面的例子中,createOrder和updateOrderStatus方法都被标记为@Transactional,表示它们需要在事务中进行操作。当这两个方法被调用时,Spring会自动开启一个事务,并在方法执行完成后自动提交事务,或者在方法发生异常时自动回滚事务。

这样,当调用OrderService的createOrder和updateOrderStatus方法时,会自动启用Spring事务管理,确保订单创建和订单状态更新在一个事务中进行,保障数据的一致性。

二、Spring事务配置介绍

Spring的使用方式有两种,即通常所说的编程式事务和声明式事务

  • 编程式事务是指应用通过使用spring提供的各个事务相关的类,通过编写代码来完成事务的设置,这种的使用门槛较高,使用难度稍大一些,而且不方便,大多数时候我们并不会用到。
  • 声明式事务则指通过配置的形式来引入spring的事务管理,具体的配置方式又可以分为通过XML文件配置和通过注解配置。由于配置方式上手容易,需要配置的内容也不多,尤其是基于注解的配置,已经成了目前引入事务的首选

(一)Spring事务属性介绍

 传播属性(传播行为)可选值说明

枚举值变量名说明
0PROPAGATION_REQUIRED当前方法必须运行在事务中。如果当前有事务,则方法将会在该事务中运行。否则就启动一个新的事务
1PROPAGATION_SUPPORTS当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行
2PROPAGATION_MANDATORY该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常
3PROPAGATION_REQUIRES_NEW当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果当前有事务则会被挂起
4PROPAGATION_NOT_SUPPORTED该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。
5PROPAGATION_NEVER当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常,这和2正好相反
6PROPAGATION_NESTED如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。

(二)声明式事务配置示例

Spring事务管理组件主要由数据源,事务管理器和代理类这三部分组成,可以用下图来表示。

 其中数据源是指定数据库的连接方式,而事务管理器则定义了使用哪一类事务管理器,不同的数据库中间件其实现得也不一样,而代理类则表示哪些类的哪些方法需要用到事务。

Spring对主流数据库都有支持,我们以mybatis为例,分别介绍基于配置文件和基于注解的配置。假设我们需要给一个org.zyf.keep.crm.service目录下的所有Service的所有update方法和insert方法加上事务,两种配置方式分别如下:

基于XML的配置

基于XML的配置方式也有很多种,这里选择最精简的基于tx标签的配置,核心配置示例如下:

<!--1. 定义数据源-->
<bean id="dataSource" class="xxx.DataSource" />
<!--2. 定义事务管理器 -->
<bean name="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
</bean>
<!--3. 定义事务切面 -->
 <tx:advice id="txAdvice" transaction-manager="transactionManager">
     <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" />
      </tx:attributes>
 </tx:advice>
<!--3. 定义切点 -->
 <aop:config>
      <aop:pointcut id="pointsCut" expression="execution(* org.zyf.keep.crm.service.*Service.update*(..))" />
      <aop:advisor advice-ref="txAdvice" pointcut-ref="pointsCut" />       
</aop:config> 

基于注解的配置

主要配置如下:

<!--1. 定义数据源-->
<bean id="dataSource" class="xxx.DataSource" />
<!--2. 定义事务管理器 -->
<bean name="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
</bean>
<!--3. 配置注解驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

这种情况下,我们需要对使用注解的Service,在方法上或者是在类上加上注解,示例如下:

@Service
public class UserServiceImpl implements UserService {
    @Transactional
    public void update(UserDO user){
    //....
    }
}

对于注解@Transactional来说,可以不配置任何属性,也可以显示指定某些属性,该注解的属性定义如下:

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default ""; // transactionManager 的别名
    @AliasFor("value")
    String transactionManager() default ""; //value的别名
    Propagation propagation() default Propagation.REQUIRED; //传播行为
    Isolation isolation() default Isolation.DEFAULT; //隔离级别,数据库默认
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;//超时时间,数据库默认
    boolean readOnly() default false; //是否为只读事务,默认非只读
    Class<? extends Throwable>[] rollbackFor() default {}; //需要回滚的异常类
    String[] rollbackForClassName() default {}; //需要回滚的异常类名
    Class<? extends Throwable>[] noRollbackFor() default {};//不回滚的异常类
    String[] noRollbackForClassName() default {};//不回滚的异常类名
}
​

对于这个注解@Transactional来说,我们可以不配置任何属性,也可以显示指定某些属性,该注解的属性定义如下:

三、分析Spring事务实现原理

通过配置已经大体知道了spring事务管理实现的原理就是aop,不难推测,spring会提供实现事务管理相关功能的切面,切点和通知相关的类,来完成对于事务相关功能的支持。

我们以配置最简单的注解式配置为例,分析其实现方式。

(一)整体流程梳理

对于通过spring管理的业务方法来说,其处理流程大概如下,重点关注的是其配置解析,事务创建及提交回滚等相关逻辑。

 (二)核心实现分析

只对基于注解的实现方式进行分析,其它的方式大同小异,核心思想都是一致的,即通过AOP机制来创建为目标类应用上事务切面。基于注解的关键配置有两个,一个是<tx:annotation-driven transaction-manager="transactionManager"/>,另一个则是@Transactional。

annotation-driven 解析

annotation-driven 是自定义标签tx的属性,这里用到了spring的命名空间机制,关于命名空间机制我们这里不展开说,只需要记住,对于每一个自定义标签,需要有一个标签处理器即可,而这个标签处理器也不需要做太多的工作,因为spring已经提供了一个NamespaceHandlerSupport,实现了大部分功能,而自定义的处理类只需要实现init方法即可,tx对应的标签处理器是 TxNamespaceHandler,其init方法定义如下:

/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.transaction.config;

import org.w3c.dom.Element;

import org.springframework.beans.factory.xml.NamespaceHandlerSupport;

/**
 * {@code NamespaceHandler} allowing for the configuration of
 * declarative transaction management using either XML or using annotations.
 *
 * <p>This namespace handler is the central piece of functionality in the
 * Spring transaction management facilities and offers two approaches
 * to declaratively manage transactions.
 *
 * <p>One approach uses transaction semantics defined in XML using the
 * {@code <tx:advice>} elements, the other uses annotations
 * in combination with the {@code <tx:annotation-driven>} element.
 * Both approached are detailed to great extent in the Spring reference manual.
 *
 * @author Rob Harrop
 * @author Juergen Hoeller
 * @since 2.0
 */
public class TxNamespaceHandler extends NamespaceHandlerSupport {

	static final String TRANSACTION_MANAGER_ATTRIBUTE = "transaction-manager";

	static final String DEFAULT_TRANSACTION_MANAGER_BEAN_NAME = "transactionManager";


	static String getTransactionManagerName(Element element) {
		return (element.hasAttribute(TRANSACTION_MANAGER_ATTRIBUTE) ?
				element.getAttribute(TRANSACTION_MANAGER_ATTRIBUTE) : DEFAULT_TRANSACTION_MANAGER_BEAN_NAME);
	}


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

}

可见init方法主要是注册了一些bean解析器,用于解析这个标签上的一些属性。标签annotation-driven 是由 AnnotationDrivenBeanDefinitionParser 来解析的,其解析方法如下:

/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.transaction.config;

import org.w3c.dom.Element;

import org.springframework.aop.config.AopNamespaceUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.transaction.event.TransactionalEventListenerFactory;
import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor;
import org.springframework.transaction.interceptor.TransactionInterceptor;

/**
 * {@link org.springframework.beans.factory.xml.BeanDefinitionParser
 * BeanDefinitionParser} implementation that allows users to easily configure
 * all the infrastructure beans required to enable annotation-driven transaction
 * demarcation.
 *
 * <p>By default, all proxies are created as JDK proxies. This may cause some
 * problems if you are injecting objects as concrete classes rather than
 * interfaces. To overcome this restriction you can set the
 * '{@code proxy-target-class}' attribute to '{@code true}', which
 * will result in class-based proxies being created.
 *
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @author Chris Beams
 * @author Stephane Nicoll
 * @since 2.0
 */
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {

	/**
	 * Parses the {@code <tx:annotation-driven/>} tag. Will
	 * {@link AopNamespaceUtils#registerAutoProxyCreatorIfNecessary register an AutoProxyCreator}
	 * with the container as necessary.
	 */
	@Override
	public BeanDefinition parse(Element element, ParserContext parserContext) {
		registerTransactionalEventListenerFactory(parserContext);
		String mode = element.getAttribute("mode");
		if ("aspectj".equals(mode)) {
			// mode="aspectj"
			registerTransactionAspect(element, parserContext);
		}
		else {
			// mode="proxy"
			AopAutoProxyConfigurer.configureAutoProxyCreator(element, parserContext);
		}
		return null;
	}

	private void registerTransactionAspect(Element element, ParserContext parserContext) {
		String txAspectBeanName = TransactionManagementConfigUtils.TRANSACTION_ASPECT_BEAN_NAME;
		String txAspectClassName = TransactionManagementConfigUtils.TRANSACTION_ASPECT_CLASS_NAME;
		if (!parserContext.getRegistry().containsBeanDefinition(txAspectBeanName)) {
			RootBeanDefinition def = new RootBeanDefinition();
			def.setBeanClassName(txAspectClassName);
			def.setFactoryMethodName("aspectOf");
			registerTransactionManager(element, def);
			parserContext.registerBeanComponent(new BeanComponentDefinition(def, txAspectBeanName));
		}
	}

	private static void registerTransactionManager(Element element, BeanDefinition def) {
		def.getPropertyValues().add("transactionManagerBeanName",
				TxNamespaceHandler.getTransactionManagerName(element));
	}

	private void registerTransactionalEventListenerFactory(ParserContext parserContext) {
		RootBeanDefinition def = new RootBeanDefinition();
		def.setBeanClass(TransactionalEventListenerFactory.class);
		parserContext.registerBeanComponent(new BeanComponentDefinition(def,
				TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME));
	}


	/**
	 * Inner class to just introduce an AOP framework dependency when actually in proxy mode.
	 */
	private static class AopAutoProxyConfigurer {

		public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
            //1. 在必要的时候注册 InfrastructureAdvisorAutoProxyCreator,默认情况下,会注册的
			AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(parserContext, element);

			String txAdvisorBeanName = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME;
			if (!parserContext.getRegistry().containsBeanDefinition(txAdvisorBeanName)) {
				Object eleSource = parserContext.extractSource(element);

				// Create the TransactionAttributeSource definition.
				RootBeanDefinition sourceDef = new RootBeanDefinition(
						"org.springframework.transaction.annotation.AnnotationTransactionAttributeSource");
				sourceDef.setSource(eleSource);
				sourceDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
				String sourceName = parserContext.getReaderContext().registerWithGeneratedName(sourceDef);

				// Create the TransactionInterceptor definition.
				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);

				// Create the TransactionAttributeSourceAdvisor definition.
				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);

				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);
			}
		}
	}

}

这段代码的主要作用就是注册了4个类。这4个类的主要作用如下:

  • InfrastructureAdvisorAutoProxyCreator:实现了 BeanPostProcessor 接口,在 postProcessAfterInitialization 方法中会根据需要创建 Proxy 类。
  • AnnotationTransactionAttributeSource:解析事务类,得到事务配置相关信息。
  • TransactionInterceptor:事务拦截器,实现了 Advice、MethodInterceptor 接口。其属性transactionAttributeSource 引用了AnnotationTransactionAttributeSource 的实例。
  • BeanFactoryTransactionAttributeSourceAdvisor:实现了 PointcutAdvisor 接口,同时其两个属性transactionAttributeSourceadviceBeanName 分别引用了AnnotationTransactionAttributeSourceTransactionInterceptor

annotation-driven 解析简单小结

  1. tx 是一个自定义标签,由一个对应的命名处理器 TxNamespaceHandler 来实现
  2. TxNamespaceHandler 在初始化是地,为 annotation-driven 注册了一个对应的解析器 AnnotationDrivenBeanDefinitionParser
  3. 该解析器在解析的时候,注册了4个和事务管理相关的类。

代理类创建分析

对于spring 事务管理来说,也是通过切面的方式来实现,所以它符合AOP主流程,利用这个主流程,我们需要有一个Advisior的查找类,这个需要继承自 AbstractAdvisiorAutoProxyCreator,因为前者是一个抽象类。另外,需要有对应的切面(实现Advisior或者是有@Aspect注解),这个切面spring并没有让用户自行配置,所以只能是由它来提供。如果能找到这样的切面,那么就可以进行代理类的创建。否则就创建不了。

那么Spring 有没有提供,以及是如何提供的呢,答案是肯定的,提供的方式就在于上面我们分析的annotation-driven 里。前面说到解析时注册了4个类的实例,其中InfrastructureAdvisorAutoProxyCreator 正是实现了AbstractAdvisiorAutoProxyCreator接口,而BeanFactoryTransactionAttributeSourceAdvisor则正是一个切面,其类层次结构如下:

我们在开发代码中只有加了@Transactional注解的类或方法才需要被代理,这是因为事实上,在findEligibleAdvisors 方法的实现上,第一步是查找匹配的Advisors,第二步还要对第一步的结果进行过滤,过滤的逻辑就是只保留那些适合于当前bean的切面:

  • BeanFactoryTransactionAttributeSourceAdvisor 内部维护限一个Pointcut(TransactionAttributeSourcePointcut )和一个事务属性transactionAttributeSource), 并把判断的逻辑交给了这个Pointcut
  • TransactionAttributeSourcePointcut 的match方法里,其判断逻辑是是否能从类或属性上找到trransactionAttributeSource 资源
  • AnnotationTransactionAttributeSource 会将具体的查找逻辑交给 TransactionAnnotationParser 去处理(SpringTransactionAnnotationParser)
  • 最终由 SpringTransactionAnnotationParser 完成最后的处理

相关代码如下:

@Override
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement ae) {
    AnnotationAttributes attributes =  
                         AnnotatedElementUtils.getMergedAnnotationAttributes(ae,         
    Transactional.classTransactional.class);
    if (attributes != null) {
        return parseTransactionAnnotation(attributes);
    }
}
//处理注解上的一些属性
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
    RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
    Propagation propagation = attributes.getEnum("propagation");
    rbta.setPropagationBehavior(propagation.value());
    // 省略一部分
    rbta.getRollbackRules().addAll(rollBackRules);
    return rbta;
}

从代码实现上来看,判断标准就是看是否有Transactional 注解。

寻找合适通知器的时序图展示

image.png

InfrastructureAdvisorAutoProxyCreator处理器注册与实例化展示

image.png

为使用事务的目标类生成代理的时序图展示

image.png

 至此,我们就分析完了spring事务切面查询与匹配校验,最终得到的切面列表,如果不为空的话,则会作为创建代理类的参数之一,由AbstractAutoProxyCreator 来完成 最终代理类的创建,由于这个是spring aop本身的逻辑,已经与事务无关,所以就不再继续分析了。总结如下:

  1. 创建代理类的关键在于是否能查找到匹配的切面
  2. spring 在解析 annotation-driven 时,就自动注册了4个和事务相关的类的实体,其中包括切面查找类和切面类的实例
  3. 查找切面之后,还需要对切面进行过滤,以判断是否能够关联切面,该过滤方法经层层委托,最后由一个注解处理器来实现,其实现标准就是看类或方法上是否有Transactional 注解
  4. 通过上述流程,可以保证给且只给带Transactional 注解的类创建代理类。

事务处理分析

分析一下spring创建 的代理类是怎么让事务生效的。

对于创建的代理类来说,其切面就是BeanFactoryTransactionAttributeSourceAdvisor的实例,根据该类的定义,其切点很明显就是TransactionAttributeSourcePointcut,而TransactionInterceptor,则充当了通知的角色,一方面advisior在实例化的时候就引用了这个对象,并作为通知 来使用。另外一方面,这个类确实也是一个Advice,其类结构如下:

这个实现了Advice接口,用于对业务类进行业务增强。这个会在创建代理类的时候进行引入。真正在进行代理类方法调用时,会调用其invoke方法,最终会调用到invokeWithinTransaction方法,其主要逻辑如下:

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
​
    // 1. 获取并解析当时设置的事务配置属性,这个属性由方法上的注解来设置
    final TransactionAttribute txAttr =     
          getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    //2. 获取平台相关的事务管理器,这个需要手动在xml文件里配置
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    //3. 获取连接点
final String joinpointIdentification = methodIdentification(method, targetClass);
​
    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
    // 一般来说,平台的事务管理器都不会继承自CallbackPreferringPlatformTransactionManager,所以这个分支会走到。
    //4. 创建事务对象,开启事务等一系列操作
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    Object retVal = null;
    try {
        // 执行业务代码
        retVal = invocation.proceedWithInvocation();
    } catch (Throwable ex) {
        // 发生异常,完成事务,可能是回滚也可能是提交
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    } finally {
         //清理事务
        cleanupTransactionInfo(txInfo);
    }
    //提交事务
    commitTransactionAfterReturning(txInfo);
    return retVal;
}
​

上面的代码描述了事务处理的主流程,在上面的几个步骤里,事务的提交是比较简单的,事务的创建和事务的撤销,由于加入了spring管理自身的一些元素,所以相对要复杂一些,这里也进行简单的介绍。

整体总结如下:

image.png

 四、总结

Spring事务能力的支撑用到了很多知识,动态代理、AOP、反射、后置处理器等等,总的来说就是应用启动时为需要使用事务的类生成代理类,以及将事务能力(拦截逻辑)织入进去,在实例化的时候调用后置处理器的逻辑,将代理类实例化替代目标类,并放入上下文容器中,在实际调用目标类事务方法的时候,被代理类中ReflectiveMethodInvocation拦截,然后先调用拦截器中的事务逻辑,然后再调用目标类的业务逻辑,最后处理异常回滚和提交,看起来比较简单,但是框架层面提供了非常庞大的基础组件来支撑和实现事务能力,当然这些基础组件大部分都会复用,比如AOP和动态代理,在异步和缓存场景下都会用到,包括我们自己扩展一些能力出来的时候也会用到。
 

参考文献

1.Spring事务原理详解_叔牙的博客-CSDN博客  

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

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

相关文章

车身控制模块BCM(Body Control Module)

1.BCM概述 车身控制模块BCM是高集成度的芯片。BCM的英文全称是Body Control Module。其控制对象是采用高灵敏度带唤醒及睡眠检测的高频收发器&#xff0c;实现车门车窗遥控上锁与开锁、电动后视镜、中控门锁、玻璃升降装置、车灯(远光灯、近光灯、位置灯、制动灯、转向灯、雾灯…

【RocketMQ】主从模式下的消费进度管理

在【RocketMQ】消息的拉取一文中可知&#xff0c;消费者在启动的时候&#xff0c;会创建消息拉取API对象PullAPIWrapper&#xff0c;调用pullKernelImpl方法向Broker发送拉取消息的请求&#xff0c;那么在主从模式下消费者是如何选择向哪个Broker发送拉取请求的&#xff1f; 进…

【Linux】项目自动化构建工具-make/Makefile

文章目录1.make/Makefile的重要性2.规则及使用使用规则3.Makefile编译多文件1.make/Makefile的重要性 会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xf…

2023美赛春季赛Z题模型代码

已经完成模型代码&#xff0c;仅供大家参考&#xff0c;需要更多请看文末 一、问题分析 首先需要收集与奥运会举办城市/国家相关的历史数据。这需要涉及诸如经济、土地利用、人类满意度&#xff08;包括运动员和观众&#xff09;、旅行、基础设施建设、环境影响等多个方面。数…

(二)【软件设计师】计算机系统—CPU运算器控制器

文章目录一、CPU1.计算机硬件基本组成&#xff08;了解&#xff09;2.中央处理单元&#xff08;了解&#xff09;3.CPU组成4.例题二、运算器1.运算器有两个主要功能2.简要介绍运算器中各组成部件的功能三、控制器1.例题2.组成部分四、总结一、CPU 1.计算机硬件基本组成&#x…

让chatGPT当我的老师如何? 通过和chatGPT交互式学习,了解在ES中,一条JSON数据是如何写到磁盘上的

最近一直有一个问题&#xff0c;如鲠在喉。争取早一天解决&#xff0c;早一天踏踏实实的睡觉。 问题是&#xff1a;在ES中&#xff0c;一条JSON数据是如何写入到磁盘上的&#xff1f; 如何解决这个问题&#xff1f;我想到了chatGPT&#xff0c;还有lucene的学习资料。这篇文章&…

港科夜闻|香港科大(广州)创邻图数据联合实验室正式成立

关注并星标每周阅读港科夜闻建立新视野 开启新思维1、香港科大(广州)创邻图数据联合实验室正式成立。未来&#xff0c;香港科大(广州)与「创邻科技」双方将围绕万亿级大图神经网络计算框架、海量数据的时序图处理、分布式原生图数据库事务及性能优化等前沿图技术领域研究展开深…

Cocos Creator的Hello World

0、文档 Cocos官网文档 1、下载 访问cocos官网下载Cocos Dashboard 安装时选项安装 Visual Studio 2017可以不勾选 注意&#xff1a;Cocos Dashboard的快捷方式文件名为CocosDashboard.exe&#xff0c;不要误认为是安装文件 2、注册、登录 注册/登录cocos网址 3、Cocos D…

深度学习之CD数据集创建

深度学习之数据集创建1. 原始文件相关文件路径2. 数据集创建2.1 数据格式一2.2 数据格式二1. 原始文件相关文件路径 原始路径 对于label图片文件&#xff0c;通道需要为单通道&#xff0c;如果不是单通道&#xff0c;训练数据会报错 报错信息例如&#xff1a;ValueError: Mask…

自然语言处理(八):Lexical Semantics

目录 1. Sentiment Analysis 2. Lexical Database 2.1 What is Lexical Database 2.2 Definitions 2.3 Meaning Through Dictionary 2.4 WordNet 2.5 Synsets 2.6 Hypernymy Chain 3. Word Similarity 3.1 Word Similarity with Paths 3.2 超越路径长度 3.3 Abstra…

波形失真总结

失真是输入信号与输出信号在幅度比例关系、相位关系及波形形状产生变化的现象。音频功放的失真分为电失真和声失真两大类。电失真是由电路引起的&#xff0c;声失真是由还音器件扬声器引起的。电失真的类型有&#xff1a;谐波失真、互调失真、瞬态失真。声失真主要是交流接口失…

idea使用之 单词拼写检查设置 分级管理

前言 idea的智能提示使用起来非常顺手, 拼接检查也是其中一项, 有些字段什么的, 不小心将单词的字母顺序写错了, idea的拼接检查就会提示(前提是字段命名规范,并且乱序之后的不再是一个具有意义的单词), 如此一来, 就能规避很多粗心大意的错误. 但是呢, 有些时候,项目中有些自…

用Claude和Stable Diffusion绘制《武松打虎》

绘制四大名著的经典画面 现在最火爆的AI&#xff0c;分为两类&#xff0c;一个是文本生成&#xff0c;一个是图片生成。如果让这两种结合到一起来&#xff0c;会是什么样的效果的。 这样是不是可能帮我们绘制很多场景下的图片&#xff0c;比如四大名著&#xff0c;帮我们的四…

Numpy基础用法

Numpy 【Numerical Python】是一个开源的Python科学计算库&#xff0c;用于快速处理任意维度的数组。Numpy支持常见的数组和矩阵操作。对于同样的数值计算任务&#xff0c;使用Numpy比直接使用Python要简洁的多。Numpy使用ndarray对象来处理多维数组&#xff0c;该对象是一个快…

【项目分析】基于工艺融合的数控编程方法的设计与实现

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招项目的&#xff0c;按照面试常问及项目核心点整理 &#x1f970;来源&#xff1a;该项目源于数控系统迭代的实验项目 &#x1f92d;结语&#xff1a;如果有帮到你的地方&#xff0c;就点个赞和关注…

新版 Spring Security 配置的变化

进入到 SpringBoot2.7 时代&#xff0c;有小伙伴发现有一个常用的类忽然过期了&#xff1a; 在 Spring Security 时代&#xff0c;这个类可太重要了。过期的类当然可以继续使用&#xff0c;但是你要是决定别扭&#xff0c;只需要稍微看一下注释&#xff0c;基本上就明白该怎么玩…

机器视觉公司,在玩一局玩不起的游戏

导语 有个著名咨询公司曾经预测过&#xff1a;未来只有两种公司&#xff0c;是人工智能的和不赚钱的。 它可能没想到&#xff0c;还有第三种——不赚钱的AI公司。 去年我们报道过“正在消失的机器视觉公司”&#xff0c;昔日的“AI 四小龙”&#xff08; 商汤、旷视、云从、依图…

大数据 | HBase基本工作原理

前文回顾&#xff1a;MapReduce基本原理 目录 &#x1f4da;HBase基本介绍 &#x1f407;HBase的设计目标和功能特点 &#x1f407;HBase在Hadoop中的生态环境 &#x1f4da;HBase的数据模型 &#x1f407;逻辑数据模型 &#x1f407;物理存储格式 &#x1f4da;HBase基…

rust网络编程以及unsafe模块

网络层目前 IPv4 和 IPv6 分庭抗礼&#xff0c;IPv6 还未完全对 IPv4 取而代之&#xff1b;传输层除了对延迟非常敏感的应用&#xff08;比如游戏quic协议&#xff09;&#xff0c;绝大多数应用都使用 TCP&#xff1b;而在应用层&#xff0c;对用户友好&#xff0c;且对防火墙友…

PageRank算法介绍

互联网上有数百亿个网页&#xff0c;可以分为这么几类&#xff1a;不含有用信息的&#xff0c;比如垃圾邮件&#xff1b;少数人比较感兴趣的&#xff0c;但范围不是很广的&#xff0c;比如个人博客、婚礼公告或家庭像册&#xff1b;很多人感兴趣的并且十分有用的&#xff0c;比…