zoukankan      html  css  js  c++  java
  • 编码式事务

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

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

    1)PlatformTransactionManager在springboot中的使用

    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);
    }



  • 相关阅读:
    POJ3164 Command Network
    UVa11401 Triangle Counting
    UVa11174 Stand in a Line
    UVa11806 Cheerleaders
    Uva11538 Chess Queen
    Bzoj3130 [Sdoi2013]费用流
    Bzoj3262 陌上花开
    模拟25A 题解
    模拟24 题解
    模拟23 题解
  • 原文地址:https://www.cnblogs.com/lukelook/p/11133402.html
Copyright © 2011-2022 走看看