编码式事务

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

原文地址:https://www.cnblogs.com/lukelook/p/11133402.html

时间: 2024-11-05 23:36:42

编码式事务的相关文章

Spring 4 官方文档学习(十一)Web MVC 框架之编码式Servlet容器初始化

在Servlet 3.0+ 环境中,你可以编码式配置Servlet容器,用来代替或者结合 web.xml文件.下面是注册DispatcherServlet : import org.springframework.web.WebApplicationInitializer; public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(

关于spring注解式事务

使用步骤: 步骤一.在spring配置文件中引入<tx:>命名空间<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation

基于TransactionScope类的分布式隐式事务

System.Transactions 命名空间中除了上一节中提到的基于 Transaction 类的显式编程模型,还提供使用 TransactionScope 类的隐式编程模型,它与显示编程模型相比,更加方便简单,它也是MSDN中建议使用的编程模型. 下面,我们基于TransactionScope类实现上一节银行转帐的例程. 示例代码: (1)SqlHelper.cs using System; using System.Collections.Generic; using System.Li

使用事务范围实现隐式事务

TransactionScope 类提供一个简单方法,通过这一方法,您不必与事务本身交互,即可将代码块标记为参与某个事务.事务范围可以自动选择和管理环境事务.由于它易于使用并且效率很高,因此建议您在开发事务应用程序时使用 TransactionScope 类. 此外,您不必显式向事务登记资源.任何 System.Transactions 资源管理器(例如 SQL Server 2005)都可以检测到该范围创建的环境事务的存在并自动登记. 创建事务范围 下面的示例说明 TransactionSco

你在用什么思想编码:事务脚本 OR 面向对象?

最近在公司内部做技术交流的时候,说起技能提升的问题,调研大家想要培训什么,结果大出我意料,很多人想要培训:面向对象编码.于是我抛出一个问题:你觉得我们现在的代码是面向对象的吗?有人回答:是,有人回答否.我对这个问题的回答是:语法上,是了,但是架构上或者思想上,不是.我们现在的大部分代码,如果要死扣一个名词的话,那就是:事务脚本. 1:最开始的事务脚本 在 Martin Fowler 的书中,存在一个典型的 应用场景,即"收入确认"(Revenue Recognition).该"

MYSQL中默认隐式事务及利用事务DML

一:默认情况下,MySQL采用autocommit模式运行.这意味着,当您执行一个用于更新(修改)表的语句之后,MySQL立刻把更新存储到磁盘中.默认级别为不可重复读. 二:会造成隐式提交的语句以下语句(以及同义词)均隐含地结束一个事务,似乎是在执行本语句前,您已经进行了一个COMMIT. (1)ALTER FUNCTION, ALTER PROCEDURE, ALTER TABLE, BEGIN, CREATEDATABASE, CREATE FUNCTION, CREATE INDEX, C

spring使用拦截式事务的注意事项

Spring MVC 和 Spring 整合的时候,SpringMVC的springmvc.xml文件中 配置扫描包,不要包含 service的注解,Spring的applicationContext.xml文件中 配置扫描包时,不要包含controller的注解,如下所示:SpringMVC的xml配置: <context:component-scan base-package="com.insigma"> <context:exclude-filter type=

seata-分布式事务与seata

分布式事务与 Seata 分布式事务 分布式事务是个现实中很常见的现象,日常的跨行转账就是一个很典型的分布式事务. 现实中,每个银行各自管理各自的账户,在执行跨行转账时,需要确保转出账户扣费正确,转入账户增加正确的金额.在电子渠道上操作看着很简单,其后台需要执行分布式事务的处理流程有很多步骤,如果账户不平,还需要进行人工对账,中间涉及到一系列的制度和机制.这里暂且不涉及电子交易的安全可信的问题,只讨论分布式事务. 转账的数字形式上无非是一系列的电子信号,但是它是用户的财富的事实代表,其转移有法律

分布式隐式事务

public class SqlHelper { public static string GetConnection() { string connStr = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString; return connStr; }   public static int ExecuteNonQuery(string sql, params MySqlParameter