一、简介
很多业务场景需要使用事务来保证多个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