编码式事务
Posted lukelook
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编码式事务相关的知识,希望对你有一定的参考价值。
1.编程式事务:编码方式实现事务管理(代码演示为JDBC事务管理)
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);
获取事务的方法主要做两件事情:
- 获取事务对象
- 根据事务传播方式返回事务状态对象
获取事务对象,在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使用注意事项
透彻的掌握 Spring 中@transactional 的使用