编码式事务

Posted lukelook

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编码式事务相关的知识,希望对你有一定的参考价值。

1.编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理)

Spring实现编程式事务,依赖于2大类,分别是上篇文章提到的PlatformTransactionManager,与模版类TransactionTemplate(推荐使用)。下面分别详细介绍Spring是如何通过该类实现事务管理。

 

2)PlatformTransactionManager
Spring在事务管理时,对事务的处理做了极致的抽象,即PlatformTransactionManager。对事务的操作,简单地来说,只有三步操作:获取事务,提交事务,回滚事务。
public interfacePlatformTransactionManager
    // 获取事务
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException;
    // 提交事务
    voidcommit(TransactionStatus status)throws TransactionException;
    // 回滚事务
    voidrollback(TransactionStatus status)throws TransactionException;
 

当然Spring不会仅仅只提供一个接口,同时会有一个抽象模版类,实现了事务管理的具体骨架。AbstractPlatformTransactionManager类可以说是Spring事务管理的控制台,决定事务如何创建,提交和回滚。

在Spring事务管理(二)-TransactionProxyFactoryBean原理中,分析TransactionInterceptor增强时,在invoke方法中最重要的三个操作:

创建事务 createTransactionIfNecessary
异常后事务处理 completeTransactionAfterThrowing
方法执行成功后事务提交 commitTransactionAfterReturning
在具体操作中,最后都是通过事务管理器PlatformTransactionManager的接口实现来执行的,其实也就是上面列出的三个接口方法。我们分别介绍这三个方法的实现,并以DataSourceTransactionManager为实现类观察JDBC方式事务的具体实现。

1. 获取事务
getTransaction方法根据事务定义来获取事务状态,事务状态中记录了事务定义,事务对象及事务相关的资源信息。对于事务的获取,除了调用事务管理器的实现来获取事务对象本身外,另外的很重要的一点是处理了事务的传播方式。

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException 
  // 1.获取事务对象
  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();
  
 
  // 2.如果已存在事务,根据不同的事务传播方式处理获取事务
  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());
  
 
  // 3. 如果当前没有事务,不同的事务传播方式不同处理方式
  // 3.1 事务传播方式为mandatory(强制必须有事务),则抛出异常
  // 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‘");
  
  // 3.2 事务传播方式为required或required_new或nested(嵌套),创建一个新的事务状态
  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;
    
  
  // 3.3 其他事务传播方式,返回一个事务对象为null的事务状态对象
  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);
  

获取事务的方法主要做两件事情:

  1. 获取事务对象
  2. 根据事务传播方式返回事务状态对象

获取事务对象,在DataSourceTransactionManager的实现中,返回一个DataSourceTransactionObject对象

protected Object doGetTransaction()
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) 
// 从事务同步管理器中根据DataSource获取数据库连接资源    
TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;

每次执行doGetTransaction方法,即会创建一个DataSourceTransactionObject对象txObject,并从事务同步管理器中根据DataSource获取数据库连接持有对象ConnectionHolder,然后存入txObject中。**事务同步管理类持有一个ThreadLocal级别的resources对象,存储DataSource和ConnectionHolder的映射关系。**因此返回的txObject中持有的ConnectionHolder可能有值,也可能为空。而不同的事务传播方式下,事务管理的处理根据txObejct中是否存在事务有不同的处理方式。

关于关注事务传播方式的实现,很多人对事务传播方式都是一知半解,只是因为没有了解源码的实现。现在就来看看具体的实现。事务传播方式的实现分为两种情况,事务不存在和事务已经存在。isExistingTransaction方法判断事务是否存在,默认在AbstractPlatformTransactionManager抽象类中返回false,而在DataSourceTransactionManager实现中,则根据是否有数据库连接来决定。

protectedbooleanisExistingTransaction(Object transaction)
  DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
  return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
事务管理器配置
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="jdbcUrl" value="$db.jdbcUrl" />
    <property name="user" value="$user" />
    <property name="password" value="$password" />
    <property name="driverClass" value="$db.driverClass" />
     <!--连接池中保留的最小连接数。 --> 
     <property name="minPoolSize"> 
         <value>5</value> 
     </property> 
     <!--连接池中保留的最大连接数。Default: 15 --> 
     <property name="maxPoolSize"> 
         <value>30</value> 
     </property> 
     <!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> 
     <property name="initialPoolSize"> 
         <value>10</value> 
     </property> 
     <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> 
     <property name="maxIdleTime"> 
         <value>60</value> 
     </property> 
     <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --> 
     <property name="acquireIncrement"> 
         <value>5</value> 
     </property> 
     <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。  如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 --> 
     <property name="maxStatements"> 
         <value>0</value> 
     </property> 
     <!--每60秒检查所有连接池中的空闲连接。Default: 0 --> 
     <property name="idleConnectionTestPeriod"> 
         <value>60</value> 
     </property> 
     <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --> 
     <property name="acquireRetryAttempts"> 
         <value>30</value> 
     </property> 
     <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false --> 
     <property name="breakAfterAcquireFailure"> 
         <value>true</value> 
     </property> 
     <!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable等方法来提升连接测试的性能。Default: false --> 
     <property name="testConnectionOnCheckout"> 
         <value>false</value> 
     </property> 
</bean>
<!--DataSourceTransactionManager位于org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,主要用于JDBC,Mybatis框架事务管理。 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>
业务中使用代码(以测试类展示)
import java.util.Map;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations =  "classpath:spring-public.xml" )
public class test 
    @Resource
    private PlatformTransactionManager txManager;
    @Resource
    private  DataSource dataSource;
    private static JdbcTemplate jdbcTemplate;
    Logger logger=Logger.getLogger(test.class);
    private static final String INSERT_SQL = "insert into testtranstation(sd) values(?)";
    private static final String COUNT_SQL = "select count(*) from testtranstation";
    @Test
    public void testdelivery()
        //定义事务隔离级别,传播行为,
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();  
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);  
        //事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务
        TransactionStatus status = txManager.getTransaction(def);  
        jdbcTemplate = new JdbcTemplate(dataSource);
        int i = jdbcTemplate.queryForInt(COUNT_SQL);  
        System.out.println("表中记录总数:"+i);
        try   
            jdbcTemplate.update(INSERT_SQL, "1");  
            txManager.commit(status);  //提交status中绑定的事务
         catch (RuntimeException e)   
            txManager.rollback(status);  //回滚
          
        i = jdbcTemplate.queryForInt(COUNT_SQL);  
        System.out.println("表中记录总数:"+i);
    
    

 2.TransactionTemplate(推荐使用)

 

TransactionTemplate模板类使用的回调接口:

  • TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
  • TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。

还是以测试类方式展示如何实现

@Test
public void testTransactionTemplate()
    jdbcTemplate = new JdbcTemplate(dataSource);
    int i = jdbcTemplate.queryForInt(COUNT_SQL);  
    System.out.println("表中记录总数:"+i);
    //构造函数初始化TransactionTemplate
    TransactionTemplate template = new TransactionTemplate(txManager);
    template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);  
    //重写execute方法实现事务管理
    template.execute(new TransactionCallbackWithoutResult() 
        @Override
        protected void doInTransactionWithoutResult(TransactionStatus status) 
            jdbcTemplate.update(INSERT_SQL, "饿死");   //字段sd为int型,所以插入肯定失败报异常,自动回滚,代表TransactionTemplate自动管理事务
        
    );
    i = jdbcTemplate.queryForInt(COUNT_SQL);  
    System.out.println("表中记录总数:"+i);

 



以上是关于编码式事务的主要内容,如果未能解决你的问题,请参考以下文章

Spring 事务管理Transactional使用注意事项

项目实战--@Transactional 的使用

透彻的掌握 Spring 中@transactional 的使用

Spring_事务管理

@Transactional(事务讲解)和springboot 整合事务

Spring 声明式事务管理(11)