事务模型 java事务模型可分为三类:本地事务模型, 编程事务模型, 声明事务模型 本地事务:由资源管理器处理,比如JDBC Connection,通过设置自动提交模式是否禁用来使用事务,也就是说事务处理与Connection直接相关 编程事务模型:JTA事务由javax.transaction包API支持,事务处理与资源使用分离开来,由事务管理器参与协调多种事务性资源,所谓编程事务是相对容器管理事务而言的,这分别于EJB中的BMP和CMP对应。编程事务由javax.transaction.UserTransaction划分事务边界也就是使用begin/commit/rollback,当需要使用挂起/回复时,还必须用到javax.transaction.TransactionManager。使用它而不用容器管理事务的场景主要包括 客户端对远程EJB Session bean进行调用 开启JTA容器事务比较消耗资源,所以,对业务方法可以更进一步划分粒度,在方法中的莫一段使用事务 使用无状态会话BEAN时,当客户端发起多次调用BEAN的不同方法以完成一次事务时 容器管理事务:EJB使用声明式事务标识改BEAN的事务处理交给容器处理,容器可以接受远程编程式开启的事务,也可以自己开启事务,最终提交或回滚事务。在bean方法内,唯一能够做的有意义的事是:捕捉检查时异常,设置rollbackOnly 简要介绍下JTA API javax.transaction.Status JTA事务状态,常用状态为ACTIVE,MARKED_ROLLBACK,ROLLBACKED javax.transaction.Synchronization 定义了事务提交之前和提交或回滚之后执行的回调方法,这些方法一般用于清理资源,比如Session,Connection javax.transaction.Transaction 定义了操作全:事务提交,回滚,绑定或解绑资源事务与资源,获取状态,设置回滚,同步 javax.transaction.TransactionManager定义了事务的所有动作,一般由应用服务器采用jts实现。也有开源实现,如JOTM.图中可以了解事务管理器与XAResource交互,并且必须提供有XADatasource的数据源 javax.transaction.TransactionSynchronizationRegistry定义了同步,JTA1.1规范新增接口。javax.transaction.UserTransaction提供给应用客户端化分事务边界的方法 javax.transaction.xa.XAResource定义了资源管理器与事务管理器的通信 javax.transaction.xa.Xid定义了资源事务关联的唯一标识,就好比树的枝干,每个资源管理器做的事就对应一条枝干的标识,而所有的枝干都长在一个树上象征着在每个资源管理器上做的事就对应着整颗树。Xid中包括全局事务格式,标识,分支标识。
org.hibernate.Transaction统一了各种事务的具体实现 JDBCTransaction JTATransaction CMTTransaction(事务托管给应用服务器完成) 三者都认为一个session只能允许同一时刻只能由一个未提交事务,实现中大量代码主要体现在事务提交前前后后的同步问题,我们将同步分为本地同步和JTA同步,之所以分开讨论,是因为这两种同步不能同时存在以上三种之一实现中。 如果条件flushBeforeCompletionEnabled||autoCloseSessionEnabled||connectionReleaseMode ==ConnectionReleaseMode.AFTER_TRANSACTION,即事务完成之前是否flush session,事务完成之后事务自动关闭 session,连接释放是否在提交事务之后 不为真,那么认为本地同步; 如果为真,则进行如下判断 如果采用的实现类是JBCTransaction或者TransactionManager没找到或者事务已经完成(提交或回滚,未激活等)或事务被标识为回滚,则为本地同步 否则,为JTA同步。 JDBCTransaction中 如果为本地同步并且flushmode !=Flushmode.Never则刷新session 如果为本地同步,则执行org.hibernate.jdbc.JDBCContext中的beforeTransactionCompletion和afterTransactionCompletion,同时执行synchronizations集合中的各个同步类(都是javax.transaction.Synchronization实现) JTATransaction中 如果不是JTA同步,则认为是本地同步 如果是JTA同步,则同步方法的执行又JTA实现自动完成,hibernate不干预 如果是本地同步或!flushBeforeCompletionEnabled并且flushModel!=flushModel.Never,则刷新session 如果是本地同步并且JTA事务是通过Transaction begin启动,则执行beforeTransactionCompletion 如果JTA不是通过Transaction begin启动,则Transaction commit并不会真正触发提交 如果是本地同步,则执行afterTransactionCompletion CMTTransaction中 如果不是JTA同步,Transaction begin抛出异常 如果是JTA同步并且!flushBeforeCompletionEnabled并且flushModel!=flushModel.Never,则刷新缓存 三种实现的总结: JDBCTransaction只会执行本地同步 它通过java.sql.Connection实现,并会执行JdbcContext.beforeTransactionCompletion和afterTransactionCompletion JTATransaction既可以进行JTA同步,也可进行本地同步; 它通过userTransaction实现, 但在调用Transaction begin方法时会调用registerSynchronizationIfPossible而此时如果找到事务管理器,并且事务是激活的会在事务注册同步org.hibernate.transaction.CacheSynchronization,间接调用调用JdbcContext.beforeTransactionCompletion和afterTransactionCompletion 否则是本地同步。事务允许通过userTransaction.begin开始,如果不是调用Transaction begin开启事务的,那自然就不会调用JdbcContext.beforeTransactionCompletion。如果是本地同步的话,还可以调用jdbcContext.afterTransactionCompletion CMTTransaction不会进行实质性的事务开始,提交,回滚,超时,只设置回滚标志,当调用Transaction begin方法时会调用registerSynchronizationIfPossible而此时如果找到事务管理器会在事务上注册同步org.hibernate.transaction.CacheSynchronization,间接调用调用JdbcContext.beforeTransactionCompletion和afterTransactionCompletion 除了同步,必须关注回滚处理 JDBCTransaction 必须调用begin开始事务,负责提交和回滚 JTATransaction 调用begin,可能参与已经开启的事务。所以,如果只是参与性的,不能够提交,不能够回滚,只能设置rollbackOnly CMTTransaction 不能开启事务,提交事务,只能够设置rollbackOnly //$Id: JTATransaction.java 9601 2006-03-11 18:17:43Z epbernard $ package org.hibernate.transaction; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.transaction.Status; import javax.transaction.Synchronization; import javax.transaction.SystemException; import javax.transaction.TransactionManager; import javax.transaction.UserTransaction; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.Transaction; import org.hibernate.TransactionException; import org.hibernate.jdbc.JDBCContext; import org.hibernate.util.JTAHelper; /** * Implements a basic transaction strategy for JTA transactions. Instances check to * see if there is an existing JTA transaction. If none exists, a new transaction * is started. If one exists, all work is done in the existing context. The * following properties are used to locate the underlying <tt>UserTransaction</tt>: * <br><br> * <table> * <tr><td><tt>hibernate.jndi.url</tt></td><td>JNDI initial context URL</td></tr> * <tr><td><tt>hibernate.jndi.class</tt></td><td>JNDI provider class</td></tr> * <tr><td><tt>jta.UserTransaction</tt></td><td>JNDI name</td></tr> * </table> * @author Gavin King */ public class JTATransaction implements Transaction { private static final Log log = LogFactory.getLog(JTATransaction.class); private final JDBCContext jdbcContext; private final TransactionFactory.Context transactionContext; private UserTransaction ut; private boolean newTransaction; private boolean begun; private boolean commitFailed; private boolean commitSucceeded; private boolean callback; public JTATransaction( InitialContext context, String utName, JDBCContext jdbcContext, TransactionFactory.Context transactionContext ) { this.jdbcContext = jdbcContext; this.transactionContext = transactionContext; log.debug("Looking for UserTransaction under: " + utName); try { ut = (UserTransaction) context.lookup(utName); } catch (NamingException ne) { log.error("Could not find UserTransaction in JNDI", ne); throw new TransactionException("Could not find UserTransaction in JNDI: ", ne); } if (ut==null) { throw new AssertionFailure("A naming service lookup returned null"); } log.debug("Obtained UserTransaction"); } public void begin() throws HibernateException { if (begun) { return; } if (commitFailed) { throw new TransactionException("cannot re-start transaction after failed commit"); } log.debug("begin"); try { newTransaction = ut.getStatus() == Status.STATUS_NO_TRANSACTION; if (newTransaction) { ut.begin(); log.debug("Began a new JTA transaction"); } } catch (Exception e) { log.error("JTA transaction begin failed", e); throw new TransactionException("JTA transaction begin failed", e); } /*if (newTransaction) { // don't need a synchronization since we are committing // or rolling back the transaction ourselves - assuming // that we do no work in beforeTransactionCompletion() synchronization = false; }*/ boolean synchronization = jdbcContext.registerSynchronizationIfPossible(); if ( !newTransaction && !synchronization ) { log.warn("You should set hibernate.transaction.manager_lookup_class if cache is enabled"); } if (!synchronization) { //if we could not register a synchronization, //do the before/after completion callbacks //ourself (but we need to let jdbcContext //know that this is what we are going to //do, so it doesn't keep trying to register //synchronizations) callback = jdbcContext.registerCallbackIfNecessary(); } begun = true; commitSucceeded = false; jdbcContext.afterTransactionBegin(this); } public void commit() throws HibernateException { if (!begun) { throw new TransactionException("Transaction not successfully started"); } log.debug("commit"); boolean flush = !transactionContext.isFlushModeNever() && ( callback || !transactionContext.isFlushBeforeCompletionEnabled() ); if (flush) { transactionContext.managedFlush(); //if an exception occurs during flush, user must call rollback() } if (callback && newTransaction) { jdbcContext.beforeTransactionCompletion(this); } closeIfRequired(); if (newTransaction) { try { ut.commit(); commitSucceeded = true; log.debug("Committed JTA UserTransaction"); } catch (Exception e) { commitFailed = true; // so the transaction is already rolled back, by JTA spec log.error("JTA commit failed", e); throw new TransactionException("JTA commit failed: ", e); } finally { afterCommitRollback(); } } else { // this one only really needed for badly-behaved applications! // (if the TransactionManager has a Sychronization registered, // its a noop) // (actually we do need it for downgrading locks) afterCommitRollback(); } } public void rollback() throws HibernateException { if (!begun && !commitFailed) { throw new TransactionException("Transaction not successfully started"); } log.debug("rollback"); /*if (!synchronization && newTransaction && !commitFailed) { jdbcContext.beforeTransactionCompletion(this); }*/ try { closeIfRequired(); } catch (Exception e) { log.error("could not close session during rollback", e); //swallow it, and continue to roll back JTA transaction } try { if (newTransaction) { if (!commitFailed) { ut.rollback(); log.debug("Rolled back JTA UserTransaction"); } } else { ut.setRollbackOnly(); log.debug("set JTA UserTransaction to rollback only"); } } catch (Exception e) { log.error("JTA rollback failed", e); throw new TransactionException("JTA rollback failed", e); } finally { afterCommitRollback(); } } private static final int NULL = Integer.MIN_VALUE; private void afterCommitRollback() throws TransactionException { begun = false; if (callback) { // this method is a noop if there is a Synchronization! if (!newTransaction) { log.warn("You should set hibernate.transaction.manager_lookup_class if cache is enabled"); } int status=NULL; try { status = ut.getStatus(); } catch (Exception e) { log.error("Could not determine transaction status after commit", e); throw new TransactionException("Could not determine transaction status after commit", e); } finally { /*if (status!=Status.STATUS_COMMITTED && status!=Status.STATUS_ROLLEDBACK) { log.warn("Transaction not complete - you should set hibernate.transaction.manager_lookup_class if cache is enabled"); //throw exception?? }*/ jdbcContext.afterTransactionCompletion(status==Status.STATUS_COMMITTED, this); } } } public boolean wasRolledBack() throws TransactionException { //if (!begun) return false; //if (commitFailed) return true; final int status; try { status = ut.getStatus(); } catch (SystemException se) { log.error("Could not determine transaction status", se); throw new TransactionException("Could not determine transaction status", se); } if (status==Status.STATUS_UNKNOWN) { throw new TransactionException("Could not determine transaction status"); } else { return JTAHelper.isRollback(status); } } public boolean wasCommitted() throws TransactionException { //if (!begun || commitFailed) return false; final int status; try { status = ut.getStatus(); } catch (SystemException se) { log.error("Could not determine transaction status", se); throw new TransactionException("Could not determine transaction status: ", se); } if (status==Status.STATUS_UNKNOWN) { throw new TransactionException("Could not determine transaction status"); } else { return status==Status.STATUS_COMMITTED; } } public boolean isActive() throws TransactionException { if (!begun || commitFailed || commitSucceeded) return false; final int status; try { status = ut.getStatus(); } catch (SystemException se) { log.error("Could not determine transaction status", se); throw new TransactionException("Could not determine transaction status: ", se); } if (status==Status.STATUS_UNKNOWN) { throw new TransactionException("Could not determine transaction status"); } else { return status==Status.STATUS_ACTIVE; } } public void registerSynchronization(Synchronization sync) throws HibernateException { if (getTransactionManager()==null) { throw new IllegalStateException("JTA TransactionManager not available"); } else { try { getTransactionManager().getTransaction().registerSynchronization(sync); } catch (Exception e) { throw new TransactionException("could not register synchronization", e); } } } private TransactionManager getTransactionManager() { return transactionContext.getFactory().getTransactionManager(); } private void closeIfRequired() throws HibernateException { boolean close = callback && transactionContext.shouldAutoClose() && !transactionContext.isClosed(); if ( close ) { transactionContext.managedClose(); } } public void setTimeout(int seconds) { try { ut.setTransactionTimeout(seconds); } catch (SystemException se) { throw new TransactionException("could not set transaction timeout", se); } } protected UserTransaction getUserTransaction() { return ut; } }