简述spring的事务管理机制

重要的概念:

sqlSession

源码

public interface SqlSession extends Closeable {

   T selectOne(String statement);

   T selectOne(String statement, Object parameter);

   List selectList(String statement);

   List selectList(String statement, Object parameter);

   List selectList(String statement, Object parameter, RowBounds rowBounds);

   Map selectMap(String statement, String mapKey);

   Map selectMap(String statement, Object parameter, String mapKey);

   Map selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);

   Cursor selectCursor(String statement);

   Cursor selectCursor(String statement, Object parameter);

   Cursor selectCursor(String statement, Object parameter, RowBounds rowBounds);

  void select(String statement, Object parameter, ResultHandler handler);

  void select(String statement, ResultHandler handler);

  void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List flushStatements();

  @Override
  void close();

  void clearCache();

  Configuration getConfiguration();

   T getMapper(Class type);

  Connection getConnection();
}

实现 

简述spring的事务管理机制_第1张图片

概述

sqlSession是一个sql会话,对外提供SQL操作的接口,但是真正执行的是其成员变量executor,后面会说(当使用mybatis等框架时,通过是调用mapper接口内定义的方法,但实际是最终还是会调用sqlSession来执行sql)。由于spring是加载bean是单例的,所以每执行一个sql会话,默认情况下都会创建一个sqlSession,以此来保证了sqlSession的线程安全。但是当启动一个事务的时候,或者说多个SQL在同一个事务内执行的时候,sqlSession会被复用。下面是具体一条SQL是如何被执行的。

以常用实现sqlSessionTemplate为例:

  @Override
  public  T selectOne(String statement) {
    return this.sqlSessionProxy.selectOne(statement);
  }

可以看到使用代理sqlSessionProxy执行。下面是代理的初始化:

private final SqlSession sqlSessionProxy;

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  }

通过动态代理,去执行sqlSession接口里的方法,这里涉及了动态代理的知识,就不多说了,总之执行时会调用SqlSessionInterceptor的involve方法。下面是源码

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }

这里实际上是个拦截器,与spring的AOP原理类似,在调用对应的method(method.invoke)前后,加上其他的逻辑。大概就是先获取另外一个sqlSession的实现,去执行method,后面看这个SQL是不是开启了一个事务,如果不是,就直接commit,如果是,提交操作就由这个事务完成,最后再把sqlSession关掉。

第一步就是通过getSqlSession获取sqlSession的另一个实现,去真正执行method。进去看源码

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

逻辑是尝试获取sqlSessionHolder,再由sqlSessionHolder获取sqlSession,获取到了直接返回,这里就是之前说的在同一事务下,复用sqlSession的原理。sqlSessionHolder获取不到则通过openSession,新建一个sqlSession,再通过registerSessionHolder把刚刚新建的sqlSession放进一个sqlSessionHolder里,这样下一个sql执行的时候,就能直接拿到sqlSession了,但是注意这个方法其实不一定会生效,只有之前所说的同一个事务下,才会,后面会解释。下面一一看源码:

getResource

private static final ThreadLocal> resources =
			new NamedThreadLocal<>("Transactional resources");

public static Object getResource(Object key) {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Object value = doGetResource(actualKey);
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" +
					Thread.currentThread().getName() + "]");
		}
		return value;
	}



	private static Object doGetResource(Object actualKey) {
		Map 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;
	}

可以看到,核心逻辑就是从当前线程的threadLocal(resource变量)里面以当前的sessionFactory为key,获取sqlSessionHolder ,获取不到就返回null。返回null,就代表了当前sql就是独立的一个事务,不和其他sql共享会话,所以可以理解为一个事务对应一个SqlSession

再看后面的sessionHolder

private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
    SqlSession session = null;
    if (holder != null && holder.isSynchronizedWithTransaction()) {
      if (holder.getExecutorType() != executorType) {
        throw new TransientDataAccessResourceException(
            "Cannot change the ExecutorType when there is an existing transaction");
      }

      holder.requested();

      LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
      session = holder.getSqlSession();
    }
    return session;
  }

一系列校验后,最后返回holder.getSqlSession。

下面就是openSession

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

这里new的executor不是JUC的executor,这个方法实际上是由项目所使用ORM框架提供,这里是mybatis提供的实现。这里初始化的executor就是真正执行SQL的类。下面是executor源码:

package org.apache.ibatis.executor;

import java.sql.SQLException;
import java.util.List;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

   List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

   Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

  List flushStatements() throws SQLException;

  void commit(boolean required) throws SQLException;

  void rollback(boolean required) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class targetType);

  Transaction getTransaction();

  void close(boolean forceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

再看最后的registerSessionHolder

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
      Environment environment = sessionFactory.getConfiguration().getEnvironment();

      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
        LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");

        holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
        TransactionSynchronizationManager.bindResource(sessionFactory, holder);
        TransactionSynchronizationManager
            .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
        holder.setSynchronizedWithTransaction(true);
        holder.requested();
      } else {
        if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
          LOGGER.debug(() -> "SqlSession [" + session
              + "] was not registered for synchronization because DataSource is not transactional");
        } else {
          throw new TransientDataAccessResourceException(
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
        }
      }
    } else {
      LOGGER.debug(() -> "SqlSession [" + session
          + "] was not registered for synchronization because synchronization is not active");
    }

  }

逻辑是如果

TransactionSynchronizationManager.isSynchronizationActive()

这个方法返回true,才会走下面的与当前线程绑定的逻辑,包括了new一个SqlSessionHolder,把它绑进当前线程的threadLocal变量resource里等。如果返回false,没有任何效果。那这个方法返回是true还是false,就是跟事务相关的!后面会说。再挂上bindResource的代码,这个跟getResource一样都是对外提供的工具方法。

public static void bindResource(Object key, Object value) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Assert.notNull(value, "Value must not be null");
		Map map = resources.get();
		// set ThreadLocal Map if none found
		if (map == null) {
			map = new HashMap<>();
			resources.set(map);
		}
		Object oldValue = map.put(actualKey, value);
		// Transparently suppress a ResourceHolder that was marked as void...
		if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
			oldValue = null;
		}
		if (oldValue != null) {
			throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
					actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
					Thread.currentThread().getName() + "]");
		}
	}

SqlSession到此为止,下面是transaction

transaction

transaction就是事务的意思,提到事务,首先就是常说的ACID四大特性,原子性,一致性,隔离性,持久性,其中原子性和隔离性是尤为需要关注的,原子性不必多说,通俗来说就是在一个事务内的操作要么全部成功,要么全部失败。隔离性就涉及到了隔离级别。

有一点特别要注意就是在一个事务内,一行数据被其他事务修改了,在当前事务内读取这一行数据,可能不会是修改后的值,而是当前事务第一次读这行数据时的值。

举个例子:

事务A开始

select * from user where id = 1 => user={name:"tom"}

事务B开始

update user set name = "jerry"  where id = 1

事务B结束

select * from user where id = 1 => user ={name:"tom"}

事务A结束

事务B修改了,但是事务A读取的还是第一次读取的值。

再来一个例子:

select * from user where id = 1 => user ={name:"tom"}

事务A开始

事务B开始

update user set name = "jerry" where  id = 1

事务B结束

select * from user where id = 1 => user ={name:"jerry"}

事务A结束

虽然user是在事务A开始后被其他事务修改了,但事务A开始第一次查询时, user会是修改后的值。往后其他线程再去修改user,事务A查询到值依然第一次查询的值。

事务的隔离性隔离的是其他线程,但是本事务对数据进行更新操作,是可见的。这个需要注意,还有如果不使用事务,那么任何事务操作都是可见的。

这里有两个概念需要分清楚,事务的隔离级别和事务的传播方式。

前者是数据库自带的对事务的处理方式,分为

读未提交、读已提交、可重复读、串行化。

这里就不多说了。

后者是spring提供的事务的传播方式,分为

REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED

下面是源码和英文的注释:

public enum Propagation {

	/**
	 * Support a current transaction, create a new one if none exists.
	 * Analogous to EJB transaction attribute of the same name.
	 * 

This is the default setting of a transaction annotation. */ REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), /** * Support a current transaction, execute non-transactionally if none exists. * Analogous to EJB transaction attribute of the same name. *

Note: For transaction managers with transaction synchronization, * {@code SUPPORTS} is slightly different from no transaction at all, * as it defines a transaction scope that synchronization will apply for. * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) * will be shared for the entire specified scope. Note that this depends on * the actual synchronization configuration of the transaction manager. * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization */ SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), /** * Support a current transaction, throw an exception if none exists. * Analogous to EJB transaction attribute of the same name. */ MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), /** * Create a new transaction, and suspend the current transaction if one exists. * Analogous to the EJB transaction attribute of the same name. *

NOTE: Actual transaction suspension will not work out-of-the-box * on all transaction managers. This in particular applies to * {@link org.springframework.transaction.jta.JtaTransactionManager}, * which requires the {@code javax.transaction.TransactionManager} to be * made available to it (which is server-specific in standard Java EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */ REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), /** * Execute non-transactionally, suspend the current transaction if one exists. * Analogous to EJB transaction attribute of the same name. *

NOTE: Actual transaction suspension will not work out-of-the-box * on all transaction managers. This in particular applies to * {@link org.springframework.transaction.jta.JtaTransactionManager}, * which requires the {@code javax.transaction.TransactionManager} to be * made available to it (which is server-specific in standard Java EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */ NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), /** * Execute non-transactionally, throw an exception if a transaction exists. * Analogous to EJB transaction attribute of the same name. */ NEVER(TransactionDefinition.PROPAGATION_NEVER), /** * Execute within a nested transaction if a current transaction exists, * behave like {@code REQUIRED} otherwise. There is no analogous feature in EJB. *

Note: Actual creation of a nested transaction will only work on specific * transaction managers. Out of the box, this only applies to the JDBC * DataSourceTransactionManager. Some JTA providers might support nested * transactions as well. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager */ NESTED(TransactionDefinition.PROPAGATION_NESTED);

大致说下这几种传播方式的区别:

1.REQUIRED:默认的传播方式,如果当前线程的绑定了一个事务(通过threadlocal,和上文的结sqlSession是一个道理),那么就使用当前的线程,否则新建一个事务执行。举个例子

@Transactional(propagation=Propagation.REQUIRED)
method1(){
    
    method2();
}

@Transactional(propagation=Propagation.REQUIRED)
method2(){...}

这里执行method1,本地没有事务,所以新建一个,执行method2,method1新建了事务,所以method2就加入到当前已存在的事务

2.SUPPORTS:当前线程有开启事务,就加入到这个事务里,否则无事务执行,或者说每个SQL都是一个单独的事务。

3.MANDATORY:当前线程有开启事务,就加入到这个事务里,否则抛出异常

4.REQUIRES_NEW :新建一个事务,如果当前线程有事务,就把它中断挂起,这里就有两个事务了,新建的事务完成后,之前的事务再继续执行。由于事务的ACID特性,这两个事务没有任何关系,彼此独立。在一些业务场景会用到,比如某个流程需要记录日志,这个日志报异常失败了不应该把业务给回滚了,所以日志记录可以用REQUIRES_NEW。如果不用这个传播方法,那就用异步执行。同时还要注意:如果要调用一个REQUIRES_NEW的方法,那这个方法和调用者方法不要在一个类里面。

说到这,还有类似的概念,由@Transactional注解的方法,spring会自动生成代理(AOP动态代理)执行,所以要注意,一个不被@Transactional注解的方法A,和一个被@Transactional注解的方法B,如果这两个方法在同一个类里面,A方法内部调用B方法,B的事务会失效。不过反过来是可行的,b的事务生效,a方法会在b的事务内执行。

5.NON_SUPPORTED:不支持事务执行,如果当前启动了事务,就把当前事务给暂时挂起。

6. NEVER:不支持事务执行,如果当前启动了事务,就抛异常。

7.NESTED:如果当前线程启动了事务,那么就并入这个事务,作为一个嵌套事务,如果当前线程没有启用事务,则新建一个事务。作为一个嵌套事务,外部事务回滚或提交,嵌套事务也会同步回滚和提交。但是嵌套事务回滚不会导致外部事务整个回滚,嵌套事务会在外部事务创建一个存储点(savepoint),嵌套事务回滚,外部事务会回滚到这个存储点。

以上就是传播级别的简要概述,下面是一个重要的接口的说明PlatformTransactionManager即spring的事务管理器

PlatformTransactionManager的实现

简述spring的事务管理机制_第2张图片

 该接口很多不同的实现,spring依据项目注入的依赖来决定具体的实现

@Transactional注解实际是使用该接口对事务进行操作的;

下面是个例子:

public class UserService {
  private final PlatformTransactionManager transactionManager;
  public UserService(PlatformTransactionManager transactionManager) {
    this.transactionManager = transactionManager;
  }
  public void createUser() {
    TransactionStatus txStatus =
        transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
      userMapper.insertUser(user);
    } catch (Exception e) {
      transactionManager.rollback(txStatus);
      throw e;
    }
    transactionManager.commit(txStatus);
  }
}

如上所述,platformTransactionManager可以被用作提交和回滚事务,下面源码:

package org.springframework.transaction;

import org.springframework.lang.Nullable;

/**
 * This is the central interface in Spring's imperative transaction infrastructure.
 * Applications can use this directly, but it is not primarily meant as an API:
 * Typically, applications will work with either TransactionTemplate or
 * declarative transaction demarcation through AOP.
 *
 * 

For implementors, it is recommended to derive from the provided * {@link org.springframework.transaction.support.AbstractPlatformTransactionManager} * class, which pre-implements the defined propagation behavior and takes care * of transaction synchronization handling. Subclasses have to implement * template methods for specific states of the underlying transaction, * for example: begin, suspend, resume, commit. * *

The default implementations of this strategy interface are * {@link org.springframework.transaction.jta.JtaTransactionManager} and * {@link org.springframework.jdbc.datasource.DataSourceTransactionManager}, * which can serve as an implementation guide for other transaction strategies. * * @author Rod Johnson * @author Juergen Hoeller * @since 16.05.2003 * @see org.springframework.transaction.support.TransactionTemplate * @see org.springframework.transaction.interceptor.TransactionInterceptor * @see org.springframework.transaction.ReactiveTransactionManager */ public interface PlatformTransactionManager extends TransactionManager { /** * Return a currently active transaction or create a new one, according to * the specified propagation behavior. *

Note that parameters like isolation level or timeout will only be applied * to new transactions, and thus be ignored when participating in active ones. *

Furthermore, not all transaction definition settings will be supported * by every transaction manager: A proper transaction manager implementation * should throw an exception when unsupported settings are encountered. *

An exception to the above rule is the read-only flag, which should be * ignored if no explicit read-only mode is supported. Essentially, the * read-only flag is just a hint for potential optimization. * @param definition the TransactionDefinition instance (can be {@code null} for defaults), * describing propagation behavior, isolation level, timeout etc. * @return transaction status object representing the new or current transaction * @throws TransactionException in case of lookup, creation, or system errors * @throws IllegalTransactionStateException if the given transaction definition * cannot be executed (for example, if a currently active transaction is in * conflict with the specified propagation behavior) * @see TransactionDefinition#getPropagationBehavior * @see TransactionDefinition#getIsolationLevel * @see TransactionDefinition#getTimeout * @see TransactionDefinition#isReadOnly */ TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; /** * Commit the given transaction, with regard to its status. If the transaction * has been marked rollback-only programmatically, perform a rollback. *

If the transaction wasn't a new one, omit the commit for proper * participation in the surrounding transaction. If a previous transaction * has been suspended to be able to create a new one, resume the previous * transaction after committing the new one. *

Note that when the commit call completes, no matter if normally or * throwing an exception, the transaction must be fully completed and * cleaned up. No rollback call should be expected in such a case. *

If this method throws an exception other than a TransactionException, * then some before-commit error caused the commit attempt to fail. For * example, an O/R Mapping tool might have tried to flush changes to the * database right before commit, with the resulting DataAccessException * causing the transaction to fail. The original exception will be * propagated to the caller of this commit method in such a case. * @param status object returned by the {@code getTransaction} method * @throws UnexpectedRollbackException in case of an unexpected rollback * that the transaction coordinator initiated * @throws HeuristicCompletionException in case of a transaction failure * caused by a heuristic decision on the side of the transaction coordinator * @throws TransactionSystemException in case of commit or system errors * (typically caused by fundamental resource failures) * @throws IllegalTransactionStateException if the given transaction * is already completed (that is, committed or rolled back) * @see TransactionStatus#setRollbackOnly */ void commit(TransactionStatus status) throws TransactionException; /** * Perform a rollback of the given transaction. *

If the transaction wasn't a new one, just set it rollback-only for proper * participation in the surrounding transaction. If a previous transaction * has been suspended to be able to create a new one, resume the previous * transaction after rolling back the new one. *

Do not call rollback on a transaction if commit threw an exception. * The transaction will already have been completed and cleaned up when commit * returns, even in case of a commit exception. Consequently, a rollback call * after commit failure will lead to an IllegalTransactionStateException. * @param status object returned by the {@code getTransaction} method * @throws TransactionSystemException in case of rollback or system errors * (typically caused by fundamental resource failures) * @throws IllegalTransactionStateException if the given transaction * is already completed (that is, committed or rolled back) */ void rollback(TransactionStatus status) throws TransactionException; }

除了回滚和提交,还可以通过getTransaction(TransactionDefinition)创建一个不会自动提交的新事务,事务的特性由transactionDefinition指定,包括事务的隔离级别,传播行为,事务超时时间,是否是只读事务等 ,传播行为已经在上文具体解析过。

DefaultTransactionDefinition是该接口的默认实现,可以通过初始化这个类,来设置事务的特性。

getTransaction方法返回一个transactionStatus对象,该对象表示返回事务的状态,通过此对象,可以获取事务transaction对象,获取事务是否只读,获取savepoint保存点管理器,并对savepoint进行管理,如生成保存点,显式的回滚到保存点,获取事务是否有保存点等,此外还能将事务标记为rollbackOnly,此后, 当这个事务被提交时,会执行回滚操作 。

下面先看下getTransaction在常用的事务管理器DataSourceTransactionManager的一个实现,更好的理解:

简述spring的事务管理机制_第3张图片

 该类首先继承了抽象类AbstractPlatformTransactionManager, getTransaction在该抽象类里实现

@Override
	public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
		Object transaction = doGetTransaction();

		// Cache debug flag to avoid repeated checks.
		boolean debugEnabled = logger.isDebugEnabled();

		if (definition == null) {
			// Use defaults if no transaction definition given.
			definition = new DefaultTransactionDefinition();
		}

		if (isExistingTransaction(transaction)) {
			// Existing transaction found -> check propagation behavior to find out how to behave.
			return handleExistingTransaction(definition, transaction, debugEnabled);
		}

		// Check definition settings for new transaction.
		if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
			throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
		}

		// No existing transaction found -> check propagation behavior to find out how to proceed.
		if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
			throw new IllegalTransactionStateException(
					"No existing transaction found for transaction marked with propagation 'mandatory'");
		}
		else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
				definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
				definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
			SuspendedResourcesHolder suspendedResources = suspend(null);
			if (debugEnabled) {
				logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
			}
			try {
				boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
				DefaultTransactionStatus status = newTransactionStatus(
						definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
				doBegin(transaction, definition);
				prepareSynchronization(status, definition);
				return status;
			}
			catch (RuntimeException | Error ex) {
				resume(null, suspendedResources);
				throw ex;
			}
		}
		else {
			// Create "empty" transaction: no actual transaction, but potentially synchronization.
			if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
				logger.warn("Custom isolation level specified but no actual transaction initiated; " +
						"isolation level will effectively be ignored: " + definition);
			}
			boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
			return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
		}
	}

看到代码,显然实际上获取事务,还是通过调用具体的事务管理器的方法doGetTransaction()实现的,获取到了事务对象,先判断事务是否已存在,已存在就按照之前所述的事务传播级别进行处理。后面同样是按照所设置的事务传播行为,进行不同的处理,这里对其实现不多详述。

主要是分析下doGetTransaction()和大部分场景下的会调用的startTransaction

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

首先是new一个dataSourceTransactionObject对象。后面是熟悉getResource方法,这个在之前的sqlSession也有介绍,大意就是先由当前线程获取threadLocal变量resource,resource.get()返回的是一个map,通过dataSource对象,从map获取映射的值。getResource在此前已经介绍过,这里不多介绍。

这里getResource返回的应该是空,如果当前线程没有启动过事务的话,启动过事务,则不为空。

接下来就是startTransaction方法。

private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
			boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

		boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
		DefaultTransactionStatus status = newTransactionStatus(
				definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
		doBegin(transaction, definition);
		prepareSynchronization(status, definition);
		return status;
	}

此方法在AbstractPlatformTransactionManager实现,生成status,并调用doBegin(transaction,definition)启动事务。此方法有具体的事务管理器实现。

@Override
	protected void doBegin(Object transaction, TransactionDefinition definition) {
		DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
		Connection con = null;

		try {
			if (!txObject.hasConnectionHolder() ||
					txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
				Connection newCon = obtainDataSource().getConnection();
				if (logger.isDebugEnabled()) {
					logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
				}
				txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
			}

			txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
			con = txObject.getConnectionHolder().getConnection();

			Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
			txObject.setPreviousIsolationLevel(previousIsolationLevel);
			txObject.setReadOnly(definition.isReadOnly());

			// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
			// so we don't want to do it unnecessarily (for example if we've explicitly
			// configured the connection pool to set it already).
			if (con.getAutoCommit()) {
				txObject.setMustRestoreAutoCommit(true);
				if (logger.isDebugEnabled()) {
					logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
				}
				con.setAutoCommit(false);
			}

			prepareTransactionalConnection(con, definition);
			txObject.getConnectionHolder().setTransactionActive(true);

			int timeout = determineTimeout(definition);
			if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
				txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
			}

			// Bind the connection holder to the thread.
			if (txObject.isNewConnectionHolder()) {
				TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
			}
		}

		catch (Throwable ex) {
			if (txObject.isNewConnectionHolder()) {
				DataSourceUtils.releaseConnection(con, obtainDataSource());
				txObject.setConnectionHolder(null, false);
			}
			throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
		}
	}

如果当前事务没有设置connectionHolder或当前事务需要前文所说的ThreadLocal变量resource同步,调用项目的具体dataSource实现通过getConnection获取一个连接,并新建一个connectionHolder对象,connectionHolder持有connection对象,由此可以获取当前连接,把connectionHolder设置到当前的事务里。

后面还有 设置连接不再自动提交,把新建connection设置到前文的ThreadLocal变量resource,与当前线程绑定(bindResource)等重要的步骤。

这里就前文所述的doGetTransaction中的getResource相对应了,所以说如果当前线程启动过线程,getResource才不为空,在doGetTransaction里,如果getResource不为空,会赋值进新的事务对象,从某中程度上来说可以达到一个连接复用的目的,减少数据库连接池的消耗。

下面是bindResource以及与之相对的unbindResource源码:

public static void bindResource(Object key, Object value) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Assert.notNull(value, "Value must not be null");
		Map map = resources.get();
		// set ThreadLocal Map if none found
		if (map == null) {
			map = new HashMap<>();
			resources.set(map);
		}
		Object oldValue = map.put(actualKey, value);
		// Transparently suppress a ResourceHolder that was marked as void...
		if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
			oldValue = null;
		}
		if (oldValue != null) {
			throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
					actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
					Thread.currentThread().getName() + "]");
		}
	}


	public static Object unbindResource(Object key) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Object value = doUnbindResource(actualKey);
		if (value == null) {
			throw new IllegalStateException(
					"No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}
		return value;
	}
@Nullable
	private static Object doUnbindResource(Object actualKey) {
		Map map = resources.get();
		if (map == null) {
			return null;
		}
		Object value = map.remove(actualKey);
		// Remove entire ThreadLocal if empty...
		if (map.isEmpty()) {
			resources.remove();
		}
		// Transparently suppress a ResourceHolder that was marked as void...
		if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
			value = null;
		}
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +
					Thread.currentThread().getName() + "]");
		}
		return value;
	}

综上所述,如果不启动事务,这里的ThreadLocal变量resource,他的get方法返回null,如果启动事务,那么由于TransactionSynchronizationManager.isSynchronizationActive为true,他的get方法返回map对象,map会包含以sqlSessionFactory对象为key,一个sqlSession对象为vale的一条数据,实现单一线程下单个事务里的sqlSession复用,除了上述的一条数据外,还有一条以dataSource为key,connection对象为value的数据,实现单一线程下不同事务的连接复用。

也就是说connection对象是和线程相绑定的。一个事务正在执行,未提交也未回滚,也就是未结束的状态,那个事务所持有的连接自然不是空闲状态,当在另一个线程里开启新的事务去获取连接,连接池只能返回新的一个连接,而不能复用。同一个线程下, 不管传播行为是什么,新建事务,连接都是会复用的。

大部分项目里,连接有连接池,线程也会线程池,所以在开启事务需要注意池子被耗尽的问题。

事务管理器PlatformTransactionManager还有rollback和commit方法,用来完结当前的事务,本质上都是用traction持有的connection对象实现的。不过是rollback还是commit都是对当前事务的完结,最后会根据当前事务所持的connection是否是复用的,决定是否对connection的释放和ThreadLocal变量resource的解绑。

如何判断是否重用了connection?commit和rollback方法需要传入一个transactionStatus作为参数,由transaction内的newSynchronization的值判断,newSynchronization是true说明没有复用connection,否则说明这个是个当前的事务并没有复用connection,是第一个创建的事务。

参考前文所说的AbstractPlatformTransactionManager.getTransaction方法,可以看到,如果当前传入参数TransactionDefinition中的事务传播级别是required,required_new或nested则初始化transactionStatus的newSynchronization是true,否则是false。联想到传播行为的具体含义可以很容易知道为什么了。

由此,引申了另一个问题,就是在当前线程启动的事务,必须在当前线程完结这个事务(rollback或commit),尽管,由transactionStatus获取transaction,可以获取到connection进行实际的完结,但是,最后步骤把connection与当前线程的resource解绑肯定会失败。

实际项目使用上,可能会应对一批任务,要对他们进行处理,考虑到效率问题,需要使用多线程一个个执行,而且还要求所有任务的事务一致性。由此自然联想到,这些任务需要统一提交,统一回滚。

一个线程执行单个任务,单条任务执行完了,需要等待其他任务一起执行完毕,并根据所有任务的状态决定commit还是rollback。在任务量少的情况下,这种解决方案还行,但是在任务量大的情况下,譬如上千个任务,必然会导致线程池,和连接池耗尽的情况,因为单个线程需要等待其他线程执行。在这样的情境下, 需要综合考量线程池和连接池的大小,对所有的任务进行分割,让一个线程处理多个任务,降低并发线程的数量已经消耗的连接数量,避免两个池子被耗尽的情况。

你可能感兴趣的:(SpingBoot,spring,java,事务,spring,boot,后端)