Spring 系列 - 事务管理

时间:2024-10-12 20:25:28

文章目录

  • 测试项目搭建
  • @EnableTransactionManagement
  • 事务动态代理
  • 事务处理流程
    • 开启事务 createTransactionIfNecessary
      • getTransaction
        • doGetTransaction
        • startTransaction
    • 事务方法执行
    • 提交事务流程
    • 异常回滚流程
  • 事务传播行为
    • PROPAGATION_REQUIRED
    • PROPAGATION_REQUIRES_NEW
    • PROPAGATION_SUPPORTS
    • PROPAGATION_NEVER
    • PROPAGATION_MANDATORY
    • PROPAGATION_NESTED
  • 事务隔离级别
  • @Transactional 失效场景

测试项目搭建

maven 依赖

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <spring.version>5.3.33</spring.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
</dependencies>

数据库表

DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
  `id` varchar(10) DEFAULT NULL,
  `name` varchar(10) DEFAULT NULL,
  `money` double(14,4) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO account VALUES(1, 'zs', 5000.00);
INSERT INTO account VALUES(2, 'ls', 3000.00);

实体类

@Data
public class Account {
    private Integer id;
    private String name;
    private Float money;
}

为减少不必要的依赖,统一使用自带的JdbcTemplate操作数据库,而不用什么JPA,Hibernate之类的。下面模拟一个转账操作

public interface IAccountService {
    void transferMoney(String from, String to, float money);
}

@Service
public class IAccountServiceImpl implements IAccountService {

    @Autowired JdbcTemplate template;

    @Override
    @Transactional
    public void transferMoney(String from, String to, float money) {
        // 转出账户扣钱
        template.update("update account set money = money - ? where name = ?", money, from);
        if (money == 200) {
        	// 模拟效果
            throw new RuntimeException("throw exception intentionally!");
        }
        // 转入账户加钱
        template.update("update account set money = money + ? where name = ?", money, to);
    }
}

测试

import org.example.transaction.service.IAccountService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@EnableTransactionManagement
@ComponentScan
public class Main {

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/mysql_learn?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public TransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager tm = new DataSourceTransactionManager();
        tm.setDataSource(dataSource);
        return tm;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
        IAccountService accountService = context.getBean(IAccountService.class);
        accountService.transferMoney("zs", "ls", 200);
    }
}

@EnableTransactionManagement

@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
	boolean proxyTargetClass() default false;
	AdviceMode mode() default AdviceMode.PROXY;
	int order() default Ordered.LOWEST_PRECEDENCE;
}

关于@Import的原理可以看我这篇文章:https://blog.****.net/qq_40926260/article/details/142646615

public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
	// ......
	// importingClassMetadata 就是Main 
	@Override
	public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
		// 提取出泛型 A 的具体类型,这里显然就是@EnableTransactionManagement这个注解了
		Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
		Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");
		// 获取Main.class上@EnableTransactionManagement这个注解的属性
		AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
		if (attributes == null) {
			throw new IllegalArgumentException(String.format(
					"@%s is not present on importing class '%s' as expected",
					annType.getSimpleName(), importingClassMetadata.getClassName()));
		}
		// 切面模式配置, 默认AdviceMode.PROXY, 也就是通过代理实现
		AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
		String[] imports = selectImports(adviceMode);
		if (imports == null) {
			throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
		}
		return imports;
	}

	@Nullable
	protected abstract String[] selectImports(AdviceMode adviceMode);
}
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {

	/**
	 * Returns {@link ProxyTransactionManagementConfiguration} or
	 * {@code AspectJ(Jta)TransactionManagementConfiguration} for {@code PROXY}
	 * and {@code ASPECTJ} values of {@link EnableTransactionManagement#mode()},
	 * respectively.
	 */
	@Override
	protected String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
				return new String[] {AutoProxyRegistrar.class.getName(),
						ProxyTransactionManagementConfiguration.class.getName()};
			case ASPECTJ:
				return new String[] {determineTransactionAspectClass()};
			default:
				return null;
		}
	}

	private String determineTransactionAspectClass() {
		return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
				TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
				TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
	}

	/**
	 * The name of the AspectJ transaction management @{@code Configuration} class for JTA.
	 * @since 5.1
	 */
	public static final String JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME =
			"org.springframework.transaction.aspectj.AspectJJtaTransactionManagementConfiguration";

	/**
	 * The bean name of the internally managed TransactionalEventListenerFactory.
	 */
	public static final String TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME =
			"org.springframework.transaction.config.internalTransactionalEventListenerFactory";
}

我们这里只看AdviceMode.PROXY的情况,可以看到@EnableTransactionManagemen作用就是导入AutoProxyRegistrarProxyTransactionManagementConfiguration这两个类到容器

事务动态代理

不加事务之前,原来的模板代码

public void runTransaction() {
	Connection conn;
	try {
		conn = getConnection();
		conn.setAutocommit(false); // 开启事务的关键
		Statement stmt ...
		methodsThatRequireTransactions();  // do something
		conn.commit();
	} catch (Throwable t) {
		conn.rollback();
	}
}

现在只需要加一个注解就搞定

@Transactional
public void methodsThatRequireTransactions() {
	// do something
}

这其中的原理就是基于 AOP 动态代理,我们的事务方法作为切点,方法执行前进行事务的初始化,方法执行后进行事务提交或回滚

在这里插入图片描述

根据 AOP 的原理它是肯定要有一个 Advice 的,所以我们直接看具体的拦截器类型就可以知道事务执行流程里方法是怎么调用的,如下图所示

在这里插入图片描述

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

事务处理流程

整个事务执行流程可以直接看org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction这个方法

@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) {
		// ... 省略
		return result;
	}
	// 获取PlatformTransactionManager
	PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
	// 事务方法名
	// 这里是: org.example.transaction.service.IAccountServiceImpl.transferMoney
	final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

	if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
		// 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.
			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 {
		// ...
	}
}

开启事务 createTransactionIfNecessary

首先需要通过 createTransactionIfNecessary 方法开启事务

protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
		@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

	// If no name specified, apply method identification as transaction name.
	if (txAttr != null && txAttr.getName() == null) {
		txAttr = new DelegatingTransactionAttribute(txAttr) {
			@Override
			public String getName() {
				return joinpointIdentification;
			}
		};
	}

	TransactionStatus status = null;
	if (txAttr != null) {
		if (tm != null) {
			status = tm.getTransaction(txAttr);
		}
		// ...
	}
	return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

通过PlatformTransactionManager#getTransaction创建一个TransactionStatus对象

在这里插入图片描述

继续看PlatformTransactionManager是怎样获取事务状态的,前面示例代码中使用到的PlatformTransactionManager 的实现是 DataSourceTransactionManager。因此我们直接跟踪进 DataSourceTransactionManager 的源码中看:

getTransaction

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
		throws TransactionException {

	// Use defaults if no transaction definition given.
	TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
	// 调用子类实现的 doGetTransaction() 方法
	Object transaction = doGetTransaction();
	boolean debugEnabled = logger.isDebugEnabled();
	// 检测是否已经存在事务,此时根据传播行为来决定如何处理
	if (isExistingTransaction(transaction)) {
		// Existing transaction found -> check propagation behavior 
		// to find out how to behave.
		return handleExistingTransaction(def, transaction, debugEnabled);
	}
	
	// 进到这里表明没有已经存在的事务

	// Check definition settings for new transaction.
	if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
		// 事务超时
	}
	
	// No existing transaction found -> check propagation behavior to find out how to proceed.
	if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY)