深入解析 Spring 事务机制

news2024/11/14 15:21:49

当构建复杂的企业级应用程序时,数据一致性和可靠性是至关重要的。Spring 框架提供了强大而灵活的事务管理机制,成为开发者处理事务的首选工具。本文将深入探讨 Spring 事务的使用和原理,为大家提供全面的了解和实际应用的指导。

本文概览

  • 首先,我们将从事务的基础出发,介绍其概念、生命周期、隔离级别、传播行为。
  • 其次,我们再介绍在 Spring 中,如何应用声明式和编程式两种事务管理方式。
  • 最后,我们将深入研究 Spring 事务的原理,了解其核心组件和关键类,解析其工作原理,探索它是如何做到将事务的控制与业务逻辑进行解耦的。

事务基础

事务简介

在数据库和软件开发领域,事务是一组相关的操作,被视为不可分割的执行单位。事务具有四个关键数据,简称 ACID 属性:

  • 原子性(Atomicity):事务是原子的,它要么全部执行成功,要么完全不执行。如果事务的任何部分失败,整个事务将回滚到初始状态,不会留下部分完成的结果。
  • 一致性(Consistency):事务在执行前后,数据库的状态应保持一致。这意味着事务的执行不会破坏数据库的完整性约束,如唯一性约束、外键约束等。
  • 隔离性(Isolation):多个事务并发执行时,每个事务都应该被隔离,以防止彼此之间的干扰。数据库系统通过事务隔离级别来定义事务之间的隔离程度。
  • 持久性(Durability):一旦事务成功完成,其结果应该是持久的,即使在系统故障或重启后也应该保持。数据库系统通常通过将事务的结果写入日志文件来实现持久性。

事务的生命周期通常包括一下阶段:

  • 开始:事务开始时,系统记录数据库的初始状态。
  • 执行:事务执行相关的数据库操作,可能包括插入、更新、删除等。
  • 提交:如果事务成功执行,将对数据库的更改提交,使其成为永久性的。
  • 回滚:如果在事务执行期间发生错误或者事务被显示混滚,系统将撤销事务中的所有更改,回复数据库到事务开始时的状态。

下面我们通过一些例子来深入理解下事务的生命周期过程:

案例一:开启事务并插入一条数据,执行成功并提交事务

-- 开始事务
BEGIN;
-- 执行数据库操作,向 `user` 表中插入一条数据
INSERT INTO `user` (name,age,address) VALUE ("帅气的小张",25,"山东菏泽");
-- 提交事务
COMMIT;

案例二:开启事务插入两条数据,其中第二条数据执行异常,事务发生回滚,那么第一条数据并没有生效

-- 开始事务
BEGIN;
-- 执行操作,向 `user` 表中插入一条数据
INSERT INTO `user` (name,age,address) VALUE ("帅气的小张",25,"山东菏泽");
-- 执行一条异常操作 address 字段拼错
INSERT INTO `user` (name,age,adress) VALUE ("帅气的小张",25,"山东菏泽");
-- 回滚事务
ROLLBACK;

可以看到,在 MySQL 里,执行事务的操作包括 BEGIN(开启)、COMMIT(提交)、ROLLBACK(回滚)

案例三:使用 Spring 框架时,进行声明式事务管理

package com.markus.spring.transaction.service;

import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Objects;

/**
 * @Author: zhangchenglong06
 * @Date: 2024/2/2
 * @Description:
 */
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    @Transactional
    public void processUser() {
        User user = new User();
        user.setName("markus zhang unique time :" + System.currentTimeMillis());
        user.setAge(25);
        user.setAddress("山东菏泽");

        // 1. 先向数据库中插入一条数据
        userDao.insertUser(user);

        // 故意抛出一个异常,验证下 第一步 的操作是否会回滚
        int i = 1 / 0;

        // 2. 再查询该数据
        User queryUserByName = userDao.queryUserByName(user.getName());
        if (Objects.isNull(queryUserByName)) {
            return;
        }
        // 3. 再更新该数据到数据库中
        queryUserByName.setAddress("北京朝阳");
        userDao.updateUser(queryUserByName);
    }
}

案例四:使用 Spring 框架时,进行编程式事务管理

package com.markus.spring.transaction.service;

import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import java.util.List;
import java.util.Objects;

/**
 * @Author: zhangchenglong06
 * @Date: 2024/2/2
 * @Description:
 */
@Service
public class UserService {

    @Autowired
    private UserDao userDao;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void processUserByProgram() {
        User user = new User();
        user.setName("markus zhang unique time :" + System.currentTimeMillis());
        user.setAge(25);
        user.setAddress("山东菏泽");
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    processUser();
                } catch (Exception e) {
                    // 如果发生异常,回滚事务
                    status.setRollbackOnly();
                    throw e;
                }
            }
        });
    }

    public void processUser() {
        User user = new User();
        user.setName("markus zhang unique time :" + System.currentTimeMillis());
        user.setAge(25);
        user.setAddress("山东菏泽");

        // 1. 先向数据库中插入一条数据
        userDao.insertUser(user);

        // 故意抛出一个异常,验证下 第一步 的操作是否会回滚
        int i = 1 / 0;

        // 2. 再查询该数据
        User queryUserByName = userDao.queryUserByName(user.getName());
        if (Objects.isNull(queryUserByName)) {
            return;
        }
        // 3. 再更新该数据到数据库中
        queryUserByName.setAddress("北京朝阳");
        userDao.updateUser(queryUserByName);
    }

    public List<User> queryAllUsers() {
        List<User> users = userDao.queryUsers(-1);
        return users;
    }
}

事务的隔离级别

上面在讲隔离性的时候提到数据库通过事务隔离级别来实现隔离性,那什么是事务隔离级别呢?

它定义了多个事务之间相互影响的程度,以及它们能否同时运行。在数据库中,有四个标准的隔离级别,分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。

读未提交(Read Uncommitted)
  • 允许事务读取其他事务未提交的数据
  • 它是最低的事务隔离级别,存在脏读问题,即一个事务读取到了另一个事务未提交的数据。

我们通过一个示例来理解一下:

# session one
-- 开始事务
BEGIN;
-- 执行数据库操作,向 `user` 表中插入一条数据
INSERT INTO `user` (name,age,address) VALUE ("帅气的小张",25,"山东菏泽");
-- 提交事务
COMMIT;

# session two
SELECT * FROM `user`

按照下图执行顺序可以看到,会话二的查询操作执行时,可以读到会话一还未提交的数据。

image-20240203172425647

读已提交(Read Committed)
  • 保证一个事务提交后才被其他事务读取。
  • 解决了脏读的问题,但仍存在不可重复读问题,即一个事务(A)在两次读取之间,另一个事务(B)修改了数据,导致 A 两次读取的数据不一致。

我们先来看下 读已提交解决脏读的场景

image-20240203172856676

可以看出,再次执行事务一的插入操作后(未提交事务),事务二执行查询逻辑时,并没有查询到数据。

我们再来看下产生不可重复读的问题,也就是在同一事务中前后读取的数据不一致。

image-20240203173835615

可重复读(Repeatable Read)
  • 保证一个事务在其生命周期内多次读取同一数据时,得到的结果是一致的。
  • 解决了不可重复读的问题,但仍然可能存在幻读问题,即一个事务在两次查询之间,另一个事务插入了新的数据。

如下图所示,可以看出,可重复读事务隔离级别可以解决同一事务中读取到不同的数据问题,但实际上,这可能也是一种虚假的数据,也就是幻读。

image-20240203175706512

串行化(Serializable)
  • 这是最高的事务隔离级别,确保事务之间不会发生脏读、不可重复读和幻读。
  • 通过对事务进行串行化来避免并发问题,但可能导致性能下降,因为他阻塞了其他事务的并发执行。

image-20240203181908920

Spring 中的应用

在 Spring 中,事务隔离级别通过 Isolation 接口表示。

public enum Isolation {

	DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),

	READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),

	READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),

	REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),

	SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);


	private final int value;


	Isolation(int value) {
		this.value = value;
	}

	public int value() {
		return this.value;
	}

}

在 Spring 中,事务隔离级别可以通过@Transactional注解或者TransactionDefinition接口进行设置。例如:

通过注解:

@Transactional(isolation = Isolation.READ_COMMITTED)
public void someTransactionalMethod() {
    // 事务处理逻辑
}

通过编程:

 public void processUserByProgram() {
      User user = new User();
      user.setName("markus zhang unique time :" + System.currentTimeMillis());
      user.setAge(25);
      user.setAddress("山东菏泽");
   		// 设置 事务隔离级别
      transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
      // 执行 事务
      transactionTemplate.execute(new TransactionCallbackWithoutResult() {
          @Override
          protected void doInTransactionWithoutResult(TransactionStatus status) {
              try {
                  processUser();
              } catch (Exception e) {
                  // 如果发生异常,回滚事务
                  status.setRollbackOnly();
                  throw e;
              }
          }
      });
  }

事务的传播行为

事务的传播行为定义了当一个事务方法被另一个事务方法调用时,他们之间的交互方式,以及新事务如何与已有事务进行关联。Spring 框架引入了事务传播行为的概念,并提供了灵活的事务管理机制,使得开发者可以根据具体需求配置事务的传播行为。可以通过@Transactional注解或者编程式事务管理,Spring 允许开发这选择合适的传播行为,以适应各种业务场景。下面我们来学习下几种常见的事务传播行为。

REQUIRED(默认值)
  • 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • 这是最常见的传播行为,适用于大多数情况。
@Transactional(propagation = Propagation.REQUIRED)
public void transactionalMethod() {
    // 事务处理逻辑
}

REQUIRED_NEW
  • 总是创建一个新的事务,如果当前存在事务,则将其挂起。
  • 适用于需要独立事务运行的情况。
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transactionalMethod() {
    // 事务处理逻辑
}
SUPPORTS
  • 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。
  • 适用于不需要事务支持的场景。
@Transactional(propagation = Propagation.SUPPORTS)
public void transactionalMethod() {
    // 事务处理逻辑
}
NOT_SUPPORTED
  • 以非事务的方式执行,如果当前存在事务,则将其挂起。
  • 适用于不希望在事务中执行的场景。
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void transactionalMethod() {
    // 事务处理逻辑
}
NEVER
  • 以非事务的方式执行,如果当前存在事务,则抛出异常。
  • 适用于不希望在事务中执行,则确保不会存在事务的场景。
@Transactional(propagation = Propagation.NEVER)
public void transactionalMethod() {
    // 事务处理逻辑
}
MANDATORY
  • 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • 适用于要求在已存在的事务中运行的场景。
@Transactional(propagation = Propagation.MANDATORY)
public void transactionalMethod() {
    // 事务处理逻辑
}
NESTED
  • 如果当前存在事务,则创建一个嵌套事务,它是当前事务的子事务;如果当前没有事务,则行为类似于 REQUIRED
  • 适用于需要嵌套事务支持的场景。
@Transactional(propagation = Propagation.NESTED)
public void transactionalMethod() {
    // 事务处理逻辑
}

Spring 事务使用

在 Spring 中,事务的实现有两种:一种是声明式事务管理,也就是通过@Transactional注解声明;另一种是编程式事务管理,也就是通过程序实现。但不管是声明式事务管理还是编程式事务管理,都需要做的事情就是:

  • 配置 DataSource 数据源
  • 配置 TransactionManagement 事务管理器

代码就不在这里罗列了,可以参考一下我的github项目 中TransactionModuleApplicationConfig类。

声明式事务管理

@Transactional 详解

@Transactional 是 Spring 框架中用于声明事务属性的注解。我们可以通过在方法或类上添加@Transactional注解,来定义事务的行为,如隔离级别、传播行为、超时的。

我们先来来看下该注解的接口定义:

package org.springframework.transaction.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.TransactionDefinition;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

	@AliasFor("transactionManager")
	String value() default "";

	@AliasFor("value")
	String transactionManager() default "";

	String[] label() default {};

	Propagation propagation() default Propagation.REQUIRED;

	Isolation isolation() default Isolation.DEFAULT;

	int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;

	String timeoutString() default "";

	boolean readOnly() default false;

	Class<? extends Throwable>[] rollbackFor() default {};

	String[] rollbackForClassName() default {};

	Class<? extends Throwable>[] noRollbackFor() default {};

	String[] noRollbackForClassName() default {};

}

  • value 与 transaction : 用于指定事务管理器的名称,表示使用哪个事务管理器。这对于配置多个事务管理器的场景很有用。
  • label : 用于定义事务的标签,这是在 Spring 5.3 版本引入的新特性。具体如何使用标签取决于实现的事务管理器实现。
  • propagation : 用于指定事务方法的传播行为,决定事务方法如何与已存在的事务进行交互。
    • 可选的事务传播行为包括 REQUIRED,REQUIRED_NEW,SUPPORTS,NOT_SUPPORTS,NEVER,MANDATORY,NESTED。
    • 默认为 REQUIRED。
  • isolation : 用于指定事务的隔离级别,控制多个事务之间的相互影响。
    • 可选的事务隔离级别包括 READ_UNCOMMITTED,READ_COMMITTED,REPEATABLE_READ,SERIALIZABLE。
    • 默认为 DEFAULT,取决于 JDBC 的事务隔离级别,依赖于数据库。
  • timeout : 指定事务的超时时间,单位为秒。如果事务执行时间超过设定的超时时间,将会被回滚。默认为-1,表示没有超时时间。
  • timeoutString : 允许将超时时间表示为字符串,例如使用占位符。作用同 timeout ,控制事务的执行时间。
  • readOnly : 用于指定事务是否为只读事务。如果设置为true,表示只进行读取数据库操作,可以优化事务(无需锁定资源、减少回滚风险、提高并发性能、降低资源消耗、数据库优化器的选择)。
  • rollbackFor 与 rollbackForClassName : 用于指定在哪些异常情况下会回滚事务,可以执行异常类型的类型数组。
  • noRollbackFor 与 noRollbackForClassName : 用于指定哪些异常下不会回滚事务,可以指定异常的类型数组。
使用示例

我们来看下声明式事务管理的示例:

package com.markus.spring.transaction.service;

import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @author: markus
 * @date: 2024/2/3 10:19 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
@Service
public class SpringTransactionService {

    @Autowired
    private UserDao userDao;

    // 事务传播行为 默认 REQUIRED
    @Transactional(rollbackFor = IllegalArgumentException.class, noRollbackFor = IllegalStateException.class)
    public void method() {
        User user = new User();
        long currentTime = System.currentTimeMillis();
        System.out.println("currentTime : " + currentTime);
        user.setName("帅气的小张 " + currentTime);
        user.setAge(25);
        user.setAddress("山东菏泽");
        // 向数据库中插入一条数据
        userDao.insertUser(user);

        // 抛出该异常会回滚
//        throw new IllegalArgumentException("违规参数");

        // 抛出该异常不会回滚
        throw new IllegalStateException("违规状态");
    }

    @Transactional(readOnly = true)
    public List<User> queryUsers() {
        return userDao.queryUsers(0);
    }

}
package com.markus.spring.transaction.service;

import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.transaction.config.TransactionModuleApplicationConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * @author: markus
 * @date: 2024/2/3 10:22 PM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TransactionModuleApplicationConfig.class)
public class TransactionServiceTest {

    @Autowired
    private SpringTransactionService springTransactionService;

    @Test
    public void testMethod() {
        springTransactionService.method();
    }

    @Test
    public void testQueryUsers() {
        List<User> users = springTransactionService.queryUsers();
        users.forEach(System.out::println);
    }
}

编程式事务管理

编程式事务管理是通过编写代码显示管理事务的一种方式,相对于声明式事务管理,它更加灵活,但也需要我们更深入地理解事务管理的细节。对于一些底层 API 我们不在此处赘述,重点讲述如何通过代码来显示管理事务。

如何使用

我们直接上代码:

package com.markus.spring.transaction.service;

import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

import static com.markus.spring.data.jdbc.domain.entity.User.createUser;

/**
 * @author: markus
 * @date: 2024/2/4 12:17 AM
 * @Description:
 * @Blog: https://markuszhang.com
 * It's my honor to share what I've learned with you!
 */
@Service
public class ProgrammaticTransactionService {

    @Autowired
    private UserDao userDao;

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void method() {
        User user = createUser();
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try {
                    userDao.insertUser(user);
                } catch (Exception e) {
                    // 捕获异常,并将事务回滚
                    status.setRollbackOnly();
                    // 并将异常跑出去
                    throw e;
                }
            }
        });
    }
}

在上面声明式事务管理中,我们可以指定一些属性;同样地,编程式事务管理也可以通过TransactionTemplate设置。

image-20240204002314546

Spring 事务实现

大家在阅读完上面的内容后,应该对事务有了一定的了解和如何使用。想必大家对其底层的实现机制有一定的兴趣,接下来,我们将深入探讨 Spring 事务管理的核心组件和关键类,解析其工作原理,通过深入源码的阅读和分析,我们将更好地的理解 Spring 事务管理的内部机制,为更高效、更安全地应用事务管理功能提供基础。

在学习源码前有必要提及的是:你需要掌握 Spring AOP 以及 Bean 生命周期的相关知识。因为 Spring 事务是在 Bean 生命周期环节对符合条件的 Bean 进行代理,通过 AOP 对类或者方法进行增强。

核心组件和关键类

TransactionManagement

它是在 Spring 5.2 版本被提出来的一个用来统一表示传统事务管理和响应式事务管理的标记接口。其实现类如下所示:

image-20240206144037433

我们重点来看 PlatformTransactionManagement 及其相关派生类。PlatformTransactionManagement 定义了 事务管理 的基本操作。

public interface PlatformTransactionManager extends TransactionManager {

	/**
	 * 获取事务
	 */
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
			throws TransactionException;

	/**
	 * 事务提交
	 */
	void commit(TransactionStatus status) throws TransactionException;

	/**
	 * 事务回滚
	 */
	void rollback(TransactionStatus status) throws TransactionException;

}

对于几个比较关键类派生类介绍:

  • DataSourceTransactionManagement:它是基于 JDBC 的事务管理器,适用于单一的 JDBC 数据源
    • JdbcTransactionManagement:它继承与DataSourceTransactionManagement,与之不同的是,它更加灵活,可以与多个不同的 JDBC 数据源进行交互。
  • JtaTransactionManagement:它是基于 JTA(Java Transaction API)的事务管理器,支持分布式事务。比如多个不同的事务资源,如数据库、消息队列等。
InfrastructureAdvisorAutoProxyCreator

InfrastructureAdvisorAutoProxyCreator 用于自动创建 AOP 的代理来应用通用的基础设施增强器(Advisors),通常用于声明式事务管理、安全性检查、性能监控方面。本篇文章介绍的正是他对于声明式事务管理方面的应用。

image-20240206145517564

通过上图可以看到,InfrastructureAdvisorAutoProxyCreator 是 AOP 核心组件 AbstraAutoProxyCreator 的实现之一,目的就是创建 AOP 代理。

TransactionInterceptor

作用如其名,事务拦截器。是的,它实现了 AOP 的概念,用于在方法调用前后织入事务管理的逻辑。

image-20240206145826965

AnnotationTransactionAttributeSource

AnnotationTransactionAttributeSource 是 Spring 框架用于解析 @Transaction 注解的类之一,它实现了 TransactinAttributeSource 接口,用于从注解中解析事务属性。

image-20240206151034838

BeanFactoryTransactionAttributeSourceAdvisor

BeanFactoryTransactionAttributeSourceAdvisor 是 Spring 框架中用于基于 BeanFactory 的事务属性源的增强器(Advisor)。它的作用是根据配置的事务属性源(TransactionAttributeSource)和事务拦截器(MethodInterceptor),为目标方法生成事务增强器,实现方法的事务管理。

image-20240206174840185

工作原理

简单用一句话描述原理就是:Spring 框架通过 AOP 实现了对业务方法执行的事务管理。详细来讲就比较复杂了,需要大家掌握 Spring Bean 生命周期、AOP 拦截、事务生命周期等。

由于比较复杂,所以对于 Bean 何时以及如何被代理,我就简单说下即可,来重点讲述下 Spring 框架如何进行的事务管理。

对于本小节的叙述,如以下所示:

  • 简单说下目标 Bean 何时以及如何被代理
  • 详细说下事务构建和事务执行
  • TransactionInterceptor#invoke() 流程介绍
自动代理

Spring 通过 InfrastructureAdvisorAutoProxyCreator 实现对目标 Bean 对象的代理。具体入口在 org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization,下面来简单介绍下该方法的执行逻辑。

@Override
public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
  if (bean != null) {
    // 获取 cacheKey,设计了缓存来加速 Advisor 的构建。
    // 可以参考 
    // 		private final Map<Object, Class<?>> proxyTypes = new ConcurrentHashMap<>(16);
    // 		private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
      // 构造 Advisor 集合并创建出代理
      return wrapIfNecessary(bean, beanName, cacheKey);
    }
  }
  return bean;
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
  // 如果 beanName 不为空并且设置了自定义的目标对象,则不需要被代理。
  if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
    return bean;
  }
  // 缓存优化,历史处理过该 Bean 并且知道不需要被代理,直接返回。
  if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
    return bean;
  }
  // 如果是 Spring Framework 基础设施 Bean 或者 判断该 Bean 需要跳过,则不需要被代理
  if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
  }

  // 获取 Advice 数据集合并创建最终的代理
  // getAdvicesAndAdvisorsForBean 方法是一个查找 IoC 容器中 Advice Bean 的方法。
  // 而我们前面说的 BeanFactoryTransactionAttributeSourceAdvisor 就是在这里获取到的。
  Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
  if (specificInterceptors != DO_NOT_PROXY) {
    this.advisedBeans.put(cacheKey, Boolean.TRUE);
    // 创建代理。
    Object proxy = createProxy(
        bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
    this.proxyTypes.put(cacheKey, proxy.getClass());
    // 返回代理。
    return proxy;
  }

  this.advisedBeans.put(cacheKey, Boolean.FALSE);
  return bean;
}

上面我们看到了 Spring 是如何给 Bean 进行代理的,可能大家会有疑问:怎么判断当前 Bean 是否可以被代理呢?

这就是 AOP 框架中 Pointcut 组件来决策的,针对于事务功能上,对应了 BeanFactoryTransactionAttributeSourceAdvisor,我们前面已经介绍过,它是为目标方法生成事务增强器,其中它就包含 Pointcut 实现即 TransactionAttributeSourcePointcut。我们来看下它的内部组成,来探究下:它是如何判断当前 Bean 是否应该被代理。

abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {

	protected TransactionAttributeSourcePointcut() {
		setClassFilter(new TransactionAttributeSourceClassFilter());
	}
  
  // 判断 是否被代理 的核心方法
	@Override
	public boolean matches(Method method, Class<?> targetClass) {
   	// tas.getTransactionAttribute 获取 @Transactional 注解元信息,如果该方法携带相关数据,说明需要被事务管理。
		TransactionAttributeSource tas = getTransactionAttributeSource();
		return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
	}

	/**
	 * Obtain the underlying TransactionAttributeSource (may be {@code null}).
	 * To be implemented by subclasses.
	 */
	@Nullable
	protected abstract TransactionAttributeSource getTransactionAttributeSource();


	/**
	 * 用于过滤不需要事务管理的类
	 */
	private class TransactionAttributeSourceClassFilter implements ClassFilter {

		@Override
		public boolean matches(Class<?> clazz) {
			if (TransactionalProxy.class.isAssignableFrom(clazz) ||
					TransactionManager.class.isAssignableFrom(clazz) ||
					PersistenceExceptionTranslator.class.isAssignableFrom(clazz)) {
				return false;
			}
      // 判断 当前类是否是携带 @Transactional 注解的候选类(在类型、方法上)
			TransactionAttributeSource tas = getTransactionAttributeSource();
			return (tas == null || tas.isCandidateClass(clazz));
		}
	}

}

通过 TransactionAttributeSourcePointcut 的处理,我们可以得出上面的结论,即 Spring 框架通过 TransactinAttributeSourcePointcut 来筛选目标方法,来对方法进行事务管理逻辑增强。

通过上面的讨论,在自动代理环节,我们能得出这样一个结论:Spring 框架通过 AOP 实现了目标 Bean 的方法增强,增加事务管理的逻辑。我们把其核心类串一下:

  1. Spring 向 IoC 容器注册 InfrastructureAdvisorAutoProxyCreator Bean,
  2. 该基础设施 Bean 可以在业务Bean生命周期的初始化后(postProcessAfterInitialization)环节,对目标业务 Bean 进行拦截,获取容器注册过的事务增强器(BeanFactoryTransactionAttributeSourceAdvisor)并为目标 Bean 生成代理,完成事务管理逻辑增强。
  3. 而 BeanFactoryTransactionAttributeSourceAdvisor 中的 TransactionAttributeSourcePointcut 完成目标 Bean 及其方法的筛选拦截。
执行事务

目前,我们已经清楚业务 Bean 如何被事务管理增强,接下来我们继续讨论当应用程序调用被事务管理的方法时,事务是如何被执行的。

在核心组件介绍 BeanFactoryTransactionAttributeSourceAdvisor 时,它是一个 PoicutcutAdvisor,也就是说它是由 Pointcut 和 Advice 组成。在前面说目标方法筛选时Pointcut起到了作用。那么 Advice 则是在执行事务时起到作用,其实现则是 TransactionInterceptor。为什么说是它的,我们可以在这段代码中找到答案。

private static class AopAutoProxyConfigurer {

		public static void configureAutoProxyCreator(Element element, ParserContext parserContext) {
			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);
			}
		}
	}

至此,我们已经清晰代理方法的入口了,即 org.springframework.transaction.interceptor.TransactionInterceptor#invoke。接下来,我们就重点分析该方法。

@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
  // Work out the target class: may be {@code null}.
  // The TransactionAttributeSource should be passed the target class
  // as well as the method, which may be from an interface.
  Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

  // Adapt to TransactionAspectSupport's invokeWithinTransaction...
  return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
    @Override
    @Nullable
    public Object proceedWithInvocation() throws Throwable {
      return invocation.proceed();
    }
    @Override
    public Object getTarget() {
      return invocation.getThis();
    }
    @Override
    public Object[] getArguments() {
      return invocation.getArguments();
    }
  });
}

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
    final InvocationCallback invocation) throws Throwable {

  // If the transaction attribute is null, the method is non-transactional.
  TransactionAttributeSource tas = getTransactionAttributeSource();
  final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
  final TransactionManager tm = determineTransactionManager(txAttr);

  if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
    // 针对 响应式编程 的处理,我们不关注,先删除,大家有兴趣可以去看下
  }

  // 获取事务管理器
  PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
  // 获取目标业务方法
  final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

  if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
    // 这里就是声明式事务管理的处理逻辑 @Transactional 注解
    // Standard transaction demarcation with getTransaction and commit/rollback calls.
    // 获取 事务,这里面包括了事务的传播行为、事务隔离级别以及事务的提交、回滚等信息
    TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

    Object retVal;
    try {
      // This is an around advice: Invoke the next interceptor in the chain.
      // This will normally result in a target object being invoked.
      // 通常的 拦截器不只有一个,是一个链式的。如上面作者所述,这是一个 Around Advice,将会调用拦截器链中下一个拦截器。
      // 但 返回的结果 retVal 是 目标方法的执行结果。
      retVal = invocation.proceedWithInvocation();
    }
    catch (Throwable ex) {
      // target invocation exception
      // 这里的处理则是 对目标异常进行回滚。目标异常外的异常不进行回滚
      completeTransactionAfterThrowing(txInfo, ex);
      throw ex;
    }
    finally {
      // 清除事务信息在当前线程下
      cleanupTransactionInfo(txInfo);
    }

    if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
      // Set rollback-only in case of Vavr failure matching our rollback rules...
      TransactionStatus status = txInfo.getTransactionStatus();
      if (status != null && txAttr != null) {
        retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
      }
    }
    // 目标方法执行成功后返回结果 进行事务提交
    commitTransactionAfterReturning(txInfo);
    // 将 目标值 返回。
    return retVal;
  }

  else {
    // 针对 CallbackPreferringPlatformTransactionManager 事务管理的处理,多处理 编程式事务管理,我们暂时不关注,先删除。
  }
}

本文总结

我们总结一下,本篇文章从事务基础介绍开始,讲述了什么是事务、隔离级别、传播行为,接着又讲述了 Spring 事务的使用即声明式事务管理以及编程式事务管理,最后又讲述的 Spring 的 事务实现原理包括核心组件、关键类以及事务的工作原理。

至此,关于事务的知识点就讲述完了。如果还有其他没有覆盖到的地方,欢迎交流。😄

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

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

相关文章

ELAdmin 的 CRUD

数据表结构 弄个测试的数据表&#xff0c;不同类型的几个字段&#xff0c;表名位 mp_reply。 生成代码 ELAdmin 可以自动生成代码。 左侧目录系统工具–代码生成&#xff0c;点开以后可以看到上面创建的数据表mp_reply&#xff0c;点击配置。 进入的页面内容有两部分&#…

【Py/Java/C++三种语言详解】LeetCode每日一题240207【二叉树BFS】LeetCode2641、二叉树的堂兄弟节点II

有华为OD考试扣扣交流群可加948025485 可上全网独家的 欧弟OJ系统 练习华子OD、大厂真题 绿色聊天软件戳 od1336了解算法冲刺训练 文章目录 题目链接题目描述解题思路代码PythonJavaC时空复杂度 华为OD算法/大厂面试高频题算法练习冲刺训练 题目链接 LeetCode2641、二叉树的堂…

C++——stack与queue与容器适配器

1.stack和queue的使用 1.1stack的使用 栈这种数据结构我们应该挺熟了&#xff0c;先入后出&#xff0c;只有一个出口(出口靠栈顶近)嘛 stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类&#xff0c;这些容器类应该支持以操作&#xff1a; empty&#xff1…

6-3、T型加减速单片机程序【51单片机+L298N步进电机系列教程】

↑↑↑点击上方【目录】&#xff0c;查看本系列全部文章 摘要&#xff1a;根据前两节内容&#xff0c;已完成所有计算工作&#xff0c;本节内容介绍具体单片机程序流程及代码 一、程序流程图 根据前两节文章内容可知&#xff0c;T型加减速的关键内容是运动类型的判断以及定时…

Nacos安装,服务注册,负载均衡配置,权重配置以及环境隔离

1. 安装 首先从官网下载 nacos 安装包&#xff0c;注意是下载 nacos-server Nacos官网 | Nacos 官方社区 | Nacos 下载 | Nacos 下载完毕后&#xff0c;解压找到文件夹bin&#xff0c;文本打开startup.cmd 修改配置如下 然后双击 startup.cmd 启动 nacos服务&#xff0c;默认…

【AWS】step-functions服务编排

文章目录 step-functionsState machine typeStandard workflowsExpress workflows design skillsError handlingsaga Transaction processing控制分布式系统中的并发性 收费 作为AWS Serverless无服务器的一个重要一环 使用step-functions方法将 AWS 服务链接在一起 step-funct…

阿里云游戏服务器多少钱一个月?

阿里云游戏服务器租用价格表&#xff1a;4核16G服务器26元1个月、146元半年&#xff0c;游戏专业服务器8核32G配置90元一个月、271元3个月&#xff0c;阿里云服务器网aliyunfuwuqi.com分享阿里云游戏专用服务器详细配置和精准报价&#xff1a; 阿里云游戏服务器租用价格表 阿…

Java实现数据可视化的智慧河南大屏 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 数据模块 A4.2 数据模块 B4.3 数据模块 C4.4 数据模块 D4.5 数据模块 E 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的数据可视化的智慧河南大屏&#xff0c;包含了GDP、…

删除.git的影响、git分支切换时注意事项

一、删除.git的影响 master分支文件 dev分支文件 删除.git后 文件为删除.git前分支的文件状态。 二、git分支切换时注意事项 情景&#xff1a;如果我在分支A&#xff0c;想要跳转到分支B。 git的规矩是&#xff0c;在那个分支上进行的提交&#xff0c;就算哪个分支上的工作…

前端文件下载的多种方式

前端文件下载的多种方式。 前言a标签下载a标签常用属性介绍- target&#xff0c;href&#xff0c;download。 window.location.href下载window.open下载iframe 下载动态生成a标签下载文件url下载文件流下载blob文件流转换常用类型 使用 streamSaver 看实时下载进度 前言 如果我…

【类与对象(中)】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言 1.类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3.析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 引用 常引用 5.赋值运算符重载 5.1…

SpringCloud--Gateway解析

一、Gateway简介 Gateway是Spring Cloud官方推出的第二代微服务网关&#xff0c;它旨在提供统一的路由方式以及为微服务应用提供强大的负载均衡能力。与第一代Spring Cloud Netflix Zuul相比&#xff0c;Spring Cloud Gateway在性能、可扩展性、易用性等方面都有了显著的提升。…

Python深入理解collections模块:常见数据结构及应用场景分析

Python深入理解collections模块&#xff1a;常见数据结构及应用场景分析 介绍collections模块的常见数据结构代码演示defaultdictCounterOrderedDict 介绍 在Python编程中&#xff0c;经常需要使用一些内置的数据结构&#xff0c;如列表、字典等。然而&#xff0c;在某些特定的…

uniapp 本地存储的方式

1. uniapp 本地存储的方式 在uniapp开发中&#xff0c;本地存储是一个常见的需求。本地存储可以帮助我们在客户端保存和管理数据&#xff0c;以便在应用程序中进行持久化存储。本文将介绍uniapp中本地存储的几种方式&#xff0c;以及相关的代码示例。 1.1. 介绍 在移动应用开发…

OSPF综合实验报告

实验要求&#xff1a; 实验预览图&#xff1a; 实验分析&#xff1a; 1、对R4仅仅配置端口IP和环回&#xff0c;使用共有IP 2、对R3-R7配置MGRE环境&#xff0c;以R3为hub&#xff0c;R5、R7、R6为spoke。 3、对172.16.0.0/16 IP进行子网划分&#xff0c;使得全网IP基于该网…

数据结构第十天(排序算法总结)

目录 前言 常数时间复杂度&#xff1a;O(1) 线性时间复杂度&#xff1a;O(n) 线性对数时间复杂度&#xff1a;O(n log n) 平方时间复杂度&#xff1a;O(n^2) 对数时间复杂度&#xff1a;O(log n) 前言 排序算法的学习可以告一段落了。但算法的学习永不停止。 今天&…

用python编写爬虫,爬取二手车信息+实验报告

题目 报告要求 工程报告链接放在这里 https://download.csdn.net/download/Samature/88805518使用 1.安装jupyter notebook 2.用jupyter notebook打开工程里的ipynb文件&#xff0c;再run all就行 注意事项 可能遇到的bug 暂无&#xff0c;有的话私信我

通过dockerfile 生成自定义nginx镜像

通过dockerfile生成自定义nginx镜像 &#xff01;&#xff01;&#xff01;docker 必须在linux环境下才能进行如果你是window则需要装虚拟机 新建一个文件名字为Dockerfile&#xff0c;无需后缀 文件完整名就是Dockerfile 编写dockerfile FROM nginx RUN echo hello nginx!…

golang windows 环境搭建 环境配置

golang windows 环境搭建 环境配置 Golang学习之路一环境搭建 MacBook Linux 树莓派raspberrypi安装Golang环境 官网下载地址: https://go.dev/dl/ https://golang.google.cn/dl/ 下载对应系统版本&#xff0c;例如windows 64位系统&#xff0c;下载&#xff1a;xxx.window…

年终奖,有人欢喜有人忧

每年的年终奖&#xff0c;有人欢喜有人忧&#xff0c;这是科技圈的一种共同现象。最近&#xff0c;科技界最热门的新闻无疑是Meta公布了其Q4和2023年全年财报&#xff0c;被许多人誉为“史上最强财报”。 Meta的股价也从2022年的最低点90美元左右&#xff0c;一路飙升至现在的每…