Spring事务

一、简介

很多业务场景需要使用事务来保证多个SQL操作的原子性。Spring框架提供了以下两种方式来进行事务的操作:

1.1 编程式事务

一般编程式事务管理采用rollback和commit的方式来进行事务的提交以及异常的回滚,这种方式对业务代码侵入较高

@Service
public class MyService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void myTransactionalMethod() {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            // 执行数据库操作
            performDatabaseOperations();
            // 提交事务
            transactionManager.commit(status);
        } catch (Exception ex) {
            // 回滚事务
            transactionManager.rollback(status);
            throw ex;
        }
    }

    private void performDatabaseOperations() {
        // 具体数据库操作
    }
}

1.2 声明式事务

Spring提供了@Transactional注解来进行声明式事务的管理,可以减少对代码的侵入,和业务逻辑分离。

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, timeout = 30, rollbackFor = Exception.class)
public void myDetailedTransactionalMethod() {
    // 执行数据库操作
    performDatabaseOperations();
}

二、@Transactional使用

2.1 @Transactional注解属性

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

    @AliasFor("transactionManager")
    String value() default "";
    //事务管理器名称
    @AliasFor("value")
    String transactionManager() default "";
    //事务传播模式
    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 {};
}

2.2 隔离级别

隔离级别枚举值描述解决的问题
默认隔离级别DEFAULT使用底层数据库的默认隔离级别。若不显式指定该值,通常使用数据库默认设置。依赖数据库的默认设置
读未提交READ_UNCOMMITTED允许读取未提交的数据(脏读)。事务可以看到其他事务未提交的修改。不解决任何并发问题
读提交READ_COMMITTED只允许读取已提交的数据(防止脏读)。事务只能看到其他事务已提交的修改。防止脏读
可重复读REPEATABLE_READ保证在同一个事务内多次读取同样数据的结果是一样的(防止不可重复读)。防止脏读和不可重复读
串行化SERIALIZABLE完全隔离的级别。所有事务逐个顺序执行,防止脏读、不可重复读和幻读。此级别性能最低。防止脏读、不可重复读和幻读

@Transactional设置的隔离级别只作用于本次事务,不会修改数据库的默认配置

2.3、传播级别

举例:

有以下UserAccount用户账户类:

@Service
public class UserAccount {

    @Transactional(propagation = Propagation.REQUIRED)
    public void increase(int amount) {
        increaseDb(amount);
        addFlow(amount);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void decrease(int amount) {
        decreaseDb(amount);
        addFlow(amount);
    }
}
@Service
public class Transaction {

    @Transactional(propagation = Propagation.REQUIRED)
    public void transfer(UserAccount from, UserAccount to, int amount) {
        from.decrease(amount);
        to.increase(amount);
    }
}

如果transfer转账操作中间出现异常,则会同时回滚对from和to账户的修改

三、实现原理

@EnableTransactionManagement注解会自动装配ProxyTransactionManagementConfiguration配置类

/**
 * {@code @Configuration} class that registers the Spring infrastructure beans
 * necessary to enable proxy-based annotation-driven transaction management.
 *
 * @author Chris Beams
 * @since 3.1
 * @see EnableTransactionManagement
 * @see TransactionManagementConfigurationSelector
 */
@Configuration
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {

	@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
		advisor.setTransactionAttributeSource(transactionAttributeSource());
		advisor.setAdvice(transactionInterceptor());
		if (this.enableTx != null) {
			advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
		}
		return advisor;
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource() {
		return new AnnotationTransactionAttributeSource();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionInterceptor transactionInterceptor() {
		TransactionInterceptor interceptor = new TransactionInterceptor();
		interceptor.setTransactionAttributeSource(transactionAttributeSource());
		if (this.txManager != null) {
			interceptor.setTransactionManager(this.txManager);
		}
		return interceptor;
	}

}

这里返回了三个Bean,其中:

TransactionAttributeSource管理了事务属性,解析方法上的 @Transactional 注解上的属性值。

TransactionInterceptor 使用上面生成的事务属性,并配置一个事务管理器TransactionManager,来拦截方法并进行事务操作。

BeanFactoryTransactionAttributeSourceAdvisor将上述两个组件结合起来,组成一个切面。

下面看TransactionInterceptor的实现



/**
 * AOP Alliance MethodInterceptor for declarative transaction
 * management using the common Spring transaction infrastructure
 * ({@link org.springframework.transaction.PlatformTransactionManager}).
 *
 * <p>Derives from the {@link TransactionAspectSupport} class which
 * contains the integration with Spring's underlying transaction API.
 * TransactionInterceptor simply calls the relevant superclass methods
 * such as {@link #invokeWithinTransaction} in the correct order.
 *
 * <p>TransactionInterceptors are thread-safe.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see TransactionProxyFactoryBean
 * @see org.springframework.aop.framework.ProxyFactoryBean
 * @see org.springframework.aop.framework.ProxyFactory
 */
@SuppressWarnings("serial")
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {

	@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, invocation::proceed);
	}

}

 其中TransactionInterceptor的invoke方法调用了其父类TransactionAspectSupport的invokeWithinTransaction方法

/**
 * Base class for transactional aspects, such as the {@link TransactionInterceptor}
 * or an AspectJ aspect.
 *
 * <p>This enables the underlying Spring transaction infrastructure to be used easily
 * to implement an aspect for any aspect system.
 *
 * <p>Subclasses are responsible for calling methods in this class in the correct order.
 *
 * <p>If no transaction name has been specified in the {@code TransactionAttribute},
 * the exposed name will be the {@code fully-qualified class name + "." + method name}
 * (by default).
 *
 * <p>Uses the <b>Strategy</b> design pattern. A {@code PlatformTransactionManager}
 * implementation will perform the actual transaction management, and a
 * {@code TransactionAttributeSource} is used for determining transaction definitions.
 *
 * <p>A transaction aspect is serializable if its {@code PlatformTransactionManager}
 * and {@code TransactionAttributeSource} are serializable.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Stéphane Nicoll
 * @author Sam Brannen
 * @since 1.1
 * @see #setTransactionManager
 * @see #setTransactionAttributes
 * @see #setTransactionAttributeSource
 */
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

	// NOTE: This class must not implement Serializable because it serves as base
	// class for AspectJ aspects (which are not allowed to implement Serializable)!


	/**
	 * Key to use to store the default transaction manager.
	 */
	private static final Object DEFAULT_TRANSACTION_MANAGER_KEY = new Object();

	/**
	 * Holder to support the {@code currentTransactionStatus()} method,
	 * and to support communication between different cooperating advices
	 * (e.g. before and after advice) if the aspect involves more than a
	 * single method (as will be the case for around advice).
	 */
	private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
			new NamedThreadLocal<>("Current aspect-driven transaction");
	/**
	 * General delegate for around-advice-based subclasses, delegating to several other template
	 * methods on this class. Able to handle {@link CallbackPreferringPlatformTransactionManager}
	 * as well as regular {@link PlatformTransactionManager} implementations.
	 * @param method the Method being invoked
	 * @param targetClass the target class that we're invoking the method on
	 * @param invocation the callback to use for proceeding with the target invocation
	 * @return the return value of the method, if any
	 * @throws Throwable propagated from the target invocation
	 */
	@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 PlatformTransactionManager tm = determineTransactionManager(txAttr);
		final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
        // 声明式事务处理
		if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
			Object retVal = null;
			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);
			}
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

		else {
            // 编程式事务处理
			final ThrowableHolder throwableHolder = new ThrowableHolder();

			// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
			try {
				Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
					TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
					try {
						return invocation.proceedWithInvocation();
					}
					catch (Throwable ex) {
						if (txAttr.rollbackOn(ex)) {
							// A RuntimeException: will lead to a rollback.
							if (ex instanceof RuntimeException) {
								throw (RuntimeException) ex;
							}
							else {
								throw new ThrowableHolderException(ex);
							}
						}
						else {
							// A normal return value: will lead to a commit.
							throwableHolder.throwable = ex;
							return null;
						}
					}
					finally {
						cleanupTransactionInfo(txInfo);
					}
				});

				// Check result state: It might indicate a Throwable to rethrow.
				if (throwableHolder.throwable != null) {
					throw throwableHolder.throwable;
				}
				return result;
			}
			catch (ThrowableHolderException ex) {
				throw ex.getCause();
			}
			catch (TransactionSystemException ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
					ex2.initApplicationException(throwableHolder.throwable);
				}
				throw ex2;
			}
			catch (Throwable ex2) {
				if (throwableHolder.throwable != null) {
					logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
				}
				throw ex2;
			}
		}
	}

}

TransactionAspectSupport类实现了事务管理核心逻辑, 其中transactionInfoHolder用ThreadLocal封装了事务的上下文信息,invokeWithinTransaction方法主要利用AOP来织入TransactionManager进行事务的提交和回滚。

四、@Transactional 注解失效的原因

1、AOP失效

(1)注解修饰在非public方法或者final方法上

(2)类内部自调用方法等

2、方法内部未能正确处理异常

  (1)  异常未能正确抛出

(2)rollbackFor默认值只处理RuntimeException和Error,不会处理受检异常。

3、事务方法在不同的线程中

Spring通过ThreadLocal来管理数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。

4、使用了错误的传播特性

总结

@Transactional注解解构了业务逻辑和事务管理,简化了对事务的编程处理,但使用时应注意其运行机制,避免出现不符合预期的情况。还有不要忽略方法上的@Transactional注解,避免在有@Transactiona注解的方法内部进行外部调用等耗时操作,避免由此产生的大事务。

参考文档

 

MySQL :: MySQL 8.4 Reference Manual :: 17.7.2.1 Transaction Isolation Levels

Transaction Propagation :: Spring Framework

https://pdai.tech/md/spring/spring-x-framework-aop.html

Proxying Mechanisms :: Spring Framework

https://www.cnblogs.com/fnlingnzb-learner/p/16807712.html

https://blog.51cto.com/u_14787961/4833414

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值