springspringbootSpring

@Transactional原理

2019-08-11  本文已影响0人  lesline

Spring源码解析之事务篇

在讲解Transactional原理前,先看下spring对事务的管理都有哪些。

事务管理

spring支持编程式事务管理和声明式事务管理两种方式。

编程式事务管理
编程式事务使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。
对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务
声明式事务是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。
显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,它的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。
声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

Transactional原理

再分析源码前,现从理论上大概分析下:
纯JDBC操作数据库的基本步骤:

  1. 获取连接 Connection conn = DriverManager.getConnection()
  2. 开启事务conn.setAutoCommit(true/false);
  3. 执行CRUD
  4. 提交事务/回滚事务 conn.commit() / conn.rollback();
  5. 关闭连接 conn.close();

spring开启事务

spring开启事务主要有两种方式

spring方式

开启事务注解标记@Transactional
Spring在jdbc中提供了一个事务管理组件DataSourceTransactionManager

<!-- 配置事务管理组件 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource”> 
</bean>
<!-- 开启事务注解标记@Transactional -->
<tx:annotation-driven transaction-manager=“txManager" />  

配置上面的信息后,Spring在初始化包含Transactional注解的类时,会自动生成这些类的代理,并放置再容器中,以便备用。

spring boot

spring boot中打开事务的几种方式:
1、自动装载
spring-boot-autoconfigure jar中 spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration
@TransactionAutoConfiguration->EnableTransactionManagement
2、手功启动事务管理 @EnableTransactionManagemen
@EnableTransactionManagemen ->TransactionManagementConfigurationSelector->ProxyTransactionManagementConfiguration->TransactionInterceptor

具体实现:

::Transactional 实现事务管理是通过TransactionInterceptor拦截器工作的。::

主要关注两点:
::Spring 会调用TransactionInterceptor在目标方法执行前后进行拦截::
::获取数据库连接是通过当前线程获取的,同一线程获取的连接是同一个::

生成TransactionInterceptor切面类

我们以spring开启事务方式为例,
spring tx的入口就是在TxAdviceBeanDefinitionParser这里将解析tx的配置,生成TransactionInterceptor对象,这个也就是一个普通的切面类,只要符合AOP规则的调用都会进入此切面。

解析<tx:annotation-driven/>配置

public class TxNamespaceHandler extends NamespaceHandlerSupport {
    static final String /TRANSACTION_MANAGER_ATTRIBUTE/= "transaction-manager";
    static final String /DEFAULT_TRANSACTION_MANAGER_BEAN_NAME/= "transactionManager";
    @Override
    public void init() {
        registerBeanDefinitionParser("advice", new TxAdviceBeanDefinitionParser());
      //对<tx:annotation-driven/>标签的解析
        registerBeanDefinitionParser("annotation-driven", new AnnotationDrivenBeanDefinitionParser());
        registerBeanDefinitionParser("jta-transaction-manager", new JtaTransactionManagerBeanDefinitionParser());
    }
}

AnnotationDrivenBeanDefinitionParser类中会创建并注册以下三个类:

  1. TransactionInterceptor:MethodInterceptor-Advice
  2. BeanFactoryTransactionAttributeSourceAdvisor: PointcutAdvisor
  3. AnnotationTransactionAttributeSource: Pointcut
    并且组装了这三个类的关系,关系如下:
    BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
    advisor.setTransactionAttributeSource(new AnnotationTransactionAttributeSource());
    advisor.setAdvice(new TransactionInterceptor());

关于PointcutAdvisor与MethodInterceptor的使用方式,参考:spring aop使用 - 简书

具体实现:
BeanFactoryTransactionAttributeSourceAdvisor:实现了PointcutAdvisor接口的getPointcut()方法
TransactionInterceptor:实现了MethodInterceptor
AnnotationTransactionAttributeSource:通过调用getTransactionAttribute方法判断是否有@Transactional
TransactionAttributeSourcePointcut :实现MethodMatcher接口的matches方法,用于判断方法是否有@Transactional,内部使用了AnnotationTransactionAttributeSource
Spring AOP容器为使用@Transactional注解的类创建代理,在执行代理类的目标方法时,会调用Advisor的getAdvice获取MethodInterceptor并执行其invoke方法,BeanFactoryTransactionAttributeSourceAdvisor的getAdvice方法会返回TransactionInterceptor,它实现了MethodInterceptor。
TransactionInterceptor类中invoke方法为:

@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
        return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

最终会调用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 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;
    }
}

从上可以看出,在invokeWithinTransaction方法中有完整的事务管理。

获取数据库连接

接着我们看下如何开创建事务,进入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);//此方法获取数据库连接
        }
        else {
            if (logger.isDebugEnabled()) {
                logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
                        "] because no transaction manager has been configured");
            }
        }
    }
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

getTransaction方法的实现由DataSourceTransactionManager类提供,
DataSourceTransactionManager调用父类AbstractPlatformTransactionManager的实现,如下:

public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
    Object transaction = doGetTransaction();
    doBegin(transaction, definition);
    prepareSynchronization(status, definition);
    return status;
    }
}

doGetTransaction为DataSourceTransactionManager中方法,用于获取数据库连接。

org.springframework.jdbc.datasource.DataSourceTransactionManager
@Override
protected Object doGetTransaction() {
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();
    txObject.setSavepointAllowed(isNestedTransactionAllowed());
    ConnectionHolder conHolder =
            (ConnectionHolder) TransactionSynchronizationManager./getResource/(obtainDataSource());
    txObject.setConnectionHolder(conHolder, false);
    return txObject;
}

TransactionSynchronizationManager中getResource方法:
resources中存的就是当前线程池与当前线程中的一个连接,以后,这个线程中获取连接时都会从获取同一个连接。
::注意:resources是用ThreadLocal保存的,这也就说明了为什么开户新的线程会获取一个新的连接,也有跟之前事务没有关系了::

public abstract class TransactionSynchronizationManager {
 //resources中存的就是当前线程池与当前线程中的一个连接,以后,这个线程中获取连接时都会从获取同一个连接。
   private static final ThreadLocal<Map<Object, Object>> resources =
         new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
 
   /**
    * Retrieve a resource for the given key that is bound to the current thread.
    * @param key the key to check (usually the resource factory)
    * @return a value bound to the current thread (usually the active
    * resource object), or {@code null} if none
    * @see ResourceTransactionManager#getResourceFactory()
    */
   public static Object getResource(Object key) {
      Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
      Object value = doGetResource(actualKey);
      return value;
   }
 
   /**
    * Actually check the value of the resource that is bound for the given key.
    */
   private static Object doGetResource(Object actualKey) {
      Map<Object, Object> map = resources.get();
      if (map == null) {
         return null;
      }
      Object value = map.get(actualKey);
      // Transparently remove ResourceHolder that was marked as void...
      if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
         map.remove(actualKey);
         // Remove entire ThreadLocal if empty...
         if (map.isEmpty()) {
            resources.remove();
         }
         value = null;
      }
      return value;
   }
}

DataSourceUtils

别外也可通过DataSourceUtils类直接获取连接:

public abstract class DataSourceUtils {

   public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
      try {
         return doGetConnection(dataSource);
      }
      catch (SQLException ex) {
         throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
      }
   }
    
   public static Connection doGetConnection(DataSource dataSource) throws SQLException {
       ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
       if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
          conHolder.requested();
          if (!conHolder.hasConnection()) {
             logger.debug("Fetching resumed JDBC Connection from DataSource");
             conHolder.setConnection(dataSource.getConnection());
          }
          return conHolder.getConnection();
       }
    
       Connection con = dataSource.getConnection();
       ......

       return con;
    }
}

参考:

Spring事务实现原理详解 - 爱宝贝丶的个人空间 - 开源中国
Spring AOP和事务的相关陷阱 - lanhz - 博客园
Spring事务源码阅读笔记 - 活在夢裡 - 博客园

上一篇下一篇

猜你喜欢

热点阅读