Spring 事务探究

920 阅读5分钟

一、事务传播

1、PROPAGATION_REQUIRED(默认模式):

这个是最常见的,就是说,如果ServiceA.method调用了ServiceB.method,如果ServiceA.method开启了事务,然后ServiceB.method也声明了事务,那么ServiceB.method不会开启独立事务,而是将自己的操作放在ServiceA.method的事务中来执行,ServiceA和ServiceB任何一个报错都会导致整个事务回滚。这就是默认的行为,其实一般我们都是需要这样子的。

2、PROPAGATION_SUPPORTS:

如果ServiceA.method开了事务,那么ServiceB就将自己加入ServiceA中来运行,如果ServiceA.method没有开事务,那么ServiceB自己也不开事务

3、PROPAGATION_MANDATORY:

必须被一个开启了事务的方法来调用自己,否则报错

4、PROPAGATION_REQUIRES_NEW:

ServiceB.method强制性自己开启一个新的事务,然后ServiceA.method的事务会卡住,等ServiceB事务完了自己再继续。这就是影响的回滚了,如果ServiceA报错了,ServiceB是不会受到影响的,ServiceB报错了,ServiceA也可以选择性的回滚或者是提交。

5、PROPAGATION_NOT_SUPPORTED:

就是ServiceB.method不支持事务,ServiceA的事务执行到ServiceB那儿,就挂起来了,ServiceB用非事务方式运行结束,ServiceA事务再继续运行。这个好处就是ServiceB代码报错不会让ServiceA回滚。

6、PROPAGATION_NEVER:

不能被一个事务来调用,ServiceA.method开事务了,但是调用了ServiceB会报错

7、PROPAGATION_NESTED:

开启嵌套事务,ServiceB开启一个子事务,如果回滚的话,那么ServiceB就回滚到开启子事务的这个save point。

其实就是ServiceA里循环51调用ServiceB,第51次调用ServiceB失败了。第一个选项,就是两个事务都设置为PROPAGATION_REQUIRED就好了,ServiceB的所有操作都加入了ServiceA启动的一个大事务里去,任何一次失败都会导致整个事务的回滚;第二个选项,就是将ServiceB设置为PROPAGATION_REQUIRES_NEW,这样ServiceB的每次调用都在一个独立的事务里执行,这样的话,即使第51次报错,但是仅仅只是回滚第51次的操作,前面50次都在独立的事务里成功了,是不会回滚的。

其实一般也就PROPAGATION_REQUIRES_NEW比较常用,要的效果就是嵌套的那个事务是独立的事务,自己提交或者回滚,不影响外面的大事务,外面的大事务可以获取抛出的异常,自己决定是继续提交大事务还是回滚大事务。

二、AOP 动态代理的事务支持

其实核心的话,就是由 Controller 去调用 Service 的时候,调用的是 动态代理对象,通过标记 @Transactional 注解,通过 AOP 织入一些事务的代码。

首先开启一个事务,将要执行的逻辑都包含在事务当中,然后去执行逻辑,如果出错的话,执行 mysql 的回滚操作,如果执行成功的话,就执行提交。

事务.png

三、Spring 事务源码

spring-tx 包就是事务的源码,核心就是 org.springframework.transaction.interceptor 这个包下的内容,TransactionInterceptor 这个类是核心入口,也就是说,如果我们加了 @Transactional 之后,就会进行拦截,先走这个类的方法。

public Object invoke(final 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);

        // 这个方法的话就是核心的方法
        return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
            @Override
            public Object proceedWithInvocation() throws Throwable {
                return invocation.proceed();
            }
        });
    }

3.1 invokeWithinTransaction() 入口

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

        // 初始化一些对象实例
        final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
        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.
            // 如果标记了 @Transactional 注解,就创建一个事务对象
            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;
        }

        ... ... ... ... ...
                        
    }

3.2 createTransactionIfNecessary() 开启事务

protected TransactionInfo createTransactionIfNecessary(
            PlatformTransactionManager tm, 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);
            }
            else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
                            "] because no transaction manager has been configured");
                }
            }
        }
        return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
    }

这个开启事务的方法,太深入了,主要的流程的话,就是通过 hibernate-entitymanager 项目和 hibernate-core 项目,去进行开启,它俩底层也是在调用 jdbc ,通过我们配置的 Druid 链接池信息,获取到 Connection 对象之后,通过 setAutocommid(false) 最终开启了一个事务。

@Transactional 开启事务.png

3.3 事务开启后,执行业务逻辑

这个还是很简单的,没有什么可看的,就是通过调用拦截器的 invoke() 方法,调用相关服务的方法,去执行我们的逻辑。

3.4 事务提交

    protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
        if (txInfo != null && txInfo.hasTransaction()) {
            if (logger.isTraceEnabled()) {
                logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]");
            }
            txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
        }
    }

这个其实和上面是一样的,最终也是通过 hibernate 调用 jdbc 进行 commit

@Transactional 提交事务.png

3.4 事务回滚

@Transactional 事务回滚.png

事务的主要原理,就是通过 TransactionInterceptor 这个拦截器的 invoke() 方法,这个主入口,对其进行一系列的控制,首先就是先创建一个 Transaction 对象,通过调用 Hibernate 框架的相关 jar 包在,最终通过 jdbc 获取到 Connection 对象,通过这个对象设置 AutoCommit 属性为 false , 开启一个事务。

然后会执行我们写的业务逻辑,如果逻辑执行正常,没有报错的话,最终还是会通过 Hibernate 调用 jdbc 底层,通过 Connection.commit() 进行事务提交, 如果执行过程中出现异常的话,最终也是通过 Hibernate 调用 jdbc 底层,执行 Connection.rollback() 进行事务的回滚操作。