编程式事务模型
编程式事务模型和本地事务模型两者最大区别之一是,开发人员使用编程式模型,管理的是
事务(transaction),而不是连接(connection).在编程式事务模型中,开发人员负责开启和终止事务.在Spring 框架里,这些操作是通过使用org.springframework.transaction包下的TransactionTemplate 或PlatformTransactionManager 完成的。
在Spring 框架中,您可以选择使用TransactionTemplate 或PlatformTransactionManager 。下面的代码示例展示了如何使用TransactionTemplate技术的例子,这是更为常用的方式
public void updateTradeOrder(TradeOrderData order)
throws Exception {
transactionTemplate.execute(new TransactionCallback()
{
public Object doInTransaction(
TransactionStatus status)
{
try {
TradeOrderDAO dao = new TradeOrderDAO();
dao.updateTradeOrder(order);
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
<bean id="transactionTemplate"
class="org.springframework.transaction.support.
TransactionTemplate">
<property name="transactionManager">
<ref local="transactionManager"/>
</property>
</bean>
正如您从上面例子看到的,Spring 使用事务回调(transaction callback)将包含在业务方法中
的逻辑在事务上下文中包裹起来。
编程式事务的使用场景
虽然通常不推荐使用编程式事务,在有的场景下编程式事务还是非常有用的。编程式事务通常的用武之地是客户端发起事务(cient-initiatedtransactions)的情形。如果客户端为一个业务请求做多次远程方法调用,从道理上讲事务必须由客户端开启。使用JTA 时,就需要使用UserTransaction接口和编程式事务。对这样的需求,您必须在客户端bean 使用编程式事务,而在远程的EJB 使用声明式事务——因为事务上下文不能被传递给使用编程式事务的EJB。另一个可能的场景是使用本地的JTA 事务(localized JTAtransactions)。JTA 事务处理是非常消耗资源的。有时候您需要在每一个细节上都考虑足够的性能优化(如信用卡处理业务)。当有价值的资源(如数据库和消息队列)被消耗殆尽了,整个应用,而不是单个线程的吞吐量和整体性能将会受到严重影响。因此,为了性能调优的目的,您也许会选择在JTA 事务之外执行相当一部分代码,而在万不得已时再使用JTA。以信用卡处理为例,您也许不会在数据装载,数据校验,数据验证,以及过帐时使用JTA 事务。然而,当您需要将money 从一个账户转向开户银行时,您就需要开启事务了。这个事务将在账户处理完毕后立即终止,而
后剩下的流程都在没有事务上下文的环境下进行。这就是本地JTA 事务的例子。这种情况下,
使用声明式事务非常不便,因为它缺乏对事务何时开始,何时终止灵活的控制。
如果开发人员不具有充分的理由,最好别使用编程式事务模型。若遇到客户端开启事务,本地JTA 事务,或长时间运行事务的情况,方可考虑用之。对于其他的场景,您应该选择声明式事务模型。
Spring事务抽象
Spring提供了一致的事务管理抽象,这个抽象是spring最重要的抽象之一,他有如下优点:
1为不同的事务api提供了一致的编程模型,如JTA,JDBC,ibatis
2提供比大多数事务api更简单的易于使用的编程式事务管理API;
理解spring事务抽象
理解spring事务抽象的关键是事务策略,事务策略通过org.springframework.transaction.PlatformTransactionManager
接口定义:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
继续spring的哲学,TransactionException是unchecked的.底层的事务失败几乎都是致命的,很少情况下应用程序中的代码可以从他们中恢复,不过应用开发者依然可以捕获并处理TransactionException.
getTransaction()根据一个类型为TransactionDefiniton的参数返回一个TransactionStaus对象.返回的TransactionStatus对象可能代表一个新的或已经存在的事务。
如同j2ee事务上下文一样,一个TransactionStatus也是和执行的线程想关联的所以他是线程安全的.
TransactionDefinition接口定义:
1事务隔离基本:当前事务和其他事务的隔离的程度.
2事务传播:通常在一个事务中执行的所有代码都会在这个事务中运行.但是如果一个事务上下文已经存在.有几个选项可以指定一个事务性方法的执行行为,例如简单的在现有事务中运行(传播机制中的Required),或者挂起现有事务创建一个新的事务(Require_new).
3事务超时:事务在超时前能运行多久.
4只读状态:只读事务不修改任何数据.
TransactionStatus接口为处理事务的代码提供一个简单的控制事务执行和查询事务状态的方法.他在所有的事务api中都是相同的。
Spring编程式事务管理
Spring提供俩种方式进行编程式事务管理:
1使用TransactionTemplate
2直接使用一个PlatformTransactionManager实现
推荐使用第一种.
使用TransactionTemplate
TransactionTemplate采用和其他的spring模板如JdbcTemplate一样的设计方法,它使用回调方法,把应用程序代码从处理获得和释放资源中解脱出来(不在有tra catch finally).如同其他模板,TransactionTemplate是线程安全的.
使用实例
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="maxActive" value="30" />
<property name="initialSize" value="2" />
<property name="maxWait" value="30000" />
<property name="maxIdle" value="30" />
<property name="minIdle" value="1" />
<property name="testOnBorrow" value="false"></property>
<property name="testWhileIdle" value="true"></property>
<property name="validationQuery" value="select 1"></property>
<property name="timeBetweenEvictionRunsMillis"><value>300000</value></property>
<property name="numTestsPerEvictionRun"><value>10</value></property>
<property name="minEvictableIdleTimeMillis" value="300000"></property>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref local="dataSource"/>
</property>
</bean>
<!--spring jdbc template-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager">
<ref local="transactionManager"/>
</property>
<property name="isolationLevelName" value="ISOLATION_REPEATABLE_READ"/>
<property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
</bean>
TransactionTemplate源代码
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
//通过传入的隔离级别和传播机制 获取事务对象
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
<span style="white-space:pre"> </span>//回调执行 业务代码
result = action.doInTransaction(status);
}
<span style="white-space:pre"> </span>//各种异常回滚
catch (RuntimeException ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Error err) {
// Transactional code threw error -> rollback
rollbackOnException(status, err);
throw err;
}
catch (Exception ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
DataSourceTransactionManager中开启事务的源代码解析
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (txObject.getConnectionHolder() == null ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
<span style="white-space:pre"> </span>//从连接池获取新连接
Connection newCon = this.dataSource.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();
<span style="white-space:pre"> </span>//设置事务隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// 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");
}
<span style="white-space:pre"> </span>//关闭自动提交 重要
con.setAutoCommit(false);
}
txObject.getConnectionHolder().setTransactionActive(true);
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the session holder to the thread.
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(getDataSource(), txObject.getConnectionHolder());
}
}
<pre name="code" class="java">DataSourceUtils
源代码解析
public static Integer prepareConnectionForTransaction(Connection con, TransactionDefinition definition)
throws SQLException {
Assert.notNull(con, "No Connection specified");
// Set read-only flag.
if (definition != null && definition.isReadOnly()) {
try {
if (logger.isDebugEnabled()) {
logger.debug("Setting JDBC Connection [" + con + "] read-only");
}
con.setReadOnly(true);
}
catch (SQLException ex) {
Throwable exToCheck = ex;
while (exToCheck != null) {
if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
// Assume it's a connection timeout that would otherwise get lost: e.g. from JDBC 4.0
throw ex;
}
exToCheck = exToCheck.getCause();
}
// "read-only not supported" SQLException -> ignore, it's just a hint anyway
logger.debug("Could not set JDBC Connection read-only", ex);
}
catch (RuntimeException ex) {
Throwable exToCheck = ex;
while (exToCheck != null) {
if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
// Assume it's a connection timeout that would otherwise get lost: e.g. from Hibernate
throw ex;
}
exToCheck = exToCheck.getCause();
}
// "read-only not supported" UnsupportedOperationException -> ignore, it's just a hint anyway
logger.debug("Could not set JDBC Connection read-only", ex);
}
}
// Apply specific isolation level, if any.
Integer previousIsolationLevel = null;
if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
if (logger.isDebugEnabled()) {
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
definition.getIsolationLevel());
}
int currentIsolation = con.getTransactionIsolation();
<span style="white-space:pre"> </span>//设置事务隔离级别 重要
if (currentIsolation != definition.getIsolationLevel()) {
previousIsolationLevel = currentIsolation;
con.setTransactionIsolation(definition.getIsolationLevel());
}
}
return previousIsolationLevel;
}
如何实现线程安全
AbstractPlatformTransactionManager
/**
* Initialize transaction synchronization as appropriate.
*/
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
(definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) ?
definition.getIsolationLevel() : null);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
TransactionSynchronizationManager.initSynchronization();
}
}
public abstract class TransactionSynchronizationManager {
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<String>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<Boolean>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<Integer>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<Boolean>("Actual transaction active");
}