spring 事务源码讲解

一、创建代理对象

spring常用的事务有xml和注解两种方式,两种流程大体相同,只是创建代理对象细节略有区别,本文以注解方式进行讲解,即我们工作中最常用的@Transactional,重点讲解执行目标方法,代理对象的创建略过。
代码展示:

  • 配置类:TxConfig
  • service:AccountService
  • dao:AccountDao
  • 测试类:TestTx
  • 配置文件:db.properties
jdbc.url=jdbc:mysql://127.0.0.1:13306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8
jdbc.username=root
jdbc.password=123456
jdbc.driver-class-name=com.mysql.cj.jdbc.Driver
  • maven坐标:
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.9.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.12.RELEASE</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.10</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>
@EnableTransactionManagement
@Configuration
@PropertySource("classpath:db.properties")
public class TxConfig {

    @Value("${jdbc.url}")
    private String username;
    @Value("${jdbc.username}")
    private String password;
    @Value("${jdbc.password}")
    private String jdbcUrl;
    @Value("${jdbc.driver-class-name}")
    private String driverClass;


    @Bean
    public DruidDataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl(username);
        dataSource.setUsername(password);
        dataSource.setPassword(jdbcUrl);
        dataSource.setDriverClassName(driverClass);
        return dataSource;
    }

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

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

    @Bean
    public AccountDao accountDao() {
        AccountDao accountDao = new AccountDao();
        accountDao.setDataSource(dataSource());
        return accountDao;
    }
    @Bean
    public AccountService accountService() {
        AccountService accountService = new AccountService();
        accountService.setAccountDao(accountDao());
        return accountService;
    }
}
public class AccountService {

    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Transactional()
    public void trans(){
        accountDao.transOut(1L, 100);
        accountDao.transIn(2L, 100);
        System.out.println("转账成功");
    }
}
public class AccountDao{

    private JdbcTemplate jdbcTemplate;

    public void setDataSource(DataSource ds){
        this.jdbcTemplate = new JdbcTemplate(ds);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void transOut(Long outId, int money) {
        String sql = "UPDATE account SET balance = balance - ? WHERE id = ?";
        jdbcTemplate.update(sql, money, outId);
    }

    @Transactional()
    public void transIn(Long inId, int money) {
        String sql = "UPDATE account SET balance = balance + ? WHERE id = ?";
        jdbcTemplate.update(sql, money, inId);
        int i = 10 / 0;
    }

}
public class TestTx {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(TxConfig.class);
        context.refresh();
        AccountService accountService = context.getBean(AccountService.class);
        accountService.trans();
    }
}
  • 数据库:
    在这里插入图片描述

二、执行目标方法

可以看到accountService为cglib为我们创建的代理对象,可以理解为执行目标方法之前,会被一系列拦截器进行拦截,其中第一个拦截器CglibAopProxy$DynamicAdvisedInterceptor的intercept方法是我们重点关注的。

//这个方法拿到拦截器链,这里拿到了TransactionInterceptor
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
//执行拦截器链
retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();

在这里插入图片描述

public Object proceed() throws Throwable {
		// We start with an index of -1 and increment early.
		if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
			//执行目标方法
			return invokeJoinpoint();
		}

		Object interceptorOrInterceptionAdvice =
				this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
		if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
			// Evaluate dynamic method matcher here: static part will already have
			// been evaluated and found to match.
			InterceptorAndDynamicMethodMatcher dm =
					(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
			Class<?> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
			if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
				return dm.interceptor.invoke(this);
			}
			else {
				// Dynamic matching failed.
				// Skip this interceptor and invoke the next in the chain.
				return proceed();
			}
		}
		else {
			//执行拦截器
			return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
		}
	}

TransactionInterceptor核心处理逻辑

核心对象
  • TransactionInfo
  • TransactionAttribute
  • TransactionStatus
  • DataSourceTransactionObject
    这几个类的嵌套属性比较深,如图:
    在这里插入图片描述
@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);
		PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

		if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
			// 创建TransactionInfo对象,如果是第一次创建,newTransaction = true、newSynchronization = true,
			// 其中newTransaction关系到了事务的提交和回滚,是比较重要的标识
			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				// 执行目标方法
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// 发生异常是进行回滚
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				//切换线程的TransactionInfo对象
				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);
				}
			}
			// 提交事务、清空一系列的ThreadLocal对象,释放连接等
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}
}
  • accountDao.transOut(1L, 100);
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void transOut(Long outId, int money) {
     String sql = "UPDATE account SET balance = balance - ? WHERE id = ?";
     jdbcTemplate.update(sql, money, outId);
}

因为accountDao的transOut方法被注解修饰,且传播特性为REQUIRES_NEW,所以它不会继承AccountService.trans()的事务,而是创建一个新的,并挂起当前线程的ConnectionHolder,把自己创建的ConnectionHolder存入当前线程,等自己提交事务后,再把之前挂起的ConnectionHolder进行恢复。
可以看到newTransaction = true、newSynchronization = true,也就是后续的提交和回滚都是独立的,后续的transIn方法不会影响到它,因为它自己会进行提交。

在这里插入图片描述
这里会进行事务提交:
在这里插入图片描述
数据库已经提交:
在这里插入图片描述

  • accountDao.transIn(2L, 100);
    事务连接拿到的是AccountService.trans()的事务
  • 虽然发生了异常,但是transOut事务已经提交,所以这里不会回滚transOut的数据
@Transactional()
    public void transIn(Long inId, int money) {
        String sql = "UPDATE account SET balance = balance + ? WHERE id = ?";
        jdbcTemplate.update(sql, money, inId);
        int i = 10 / 0;
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值