JDBCTransaction JTATransaction CMTTransaction

dylanduk發表於2013-03-28


事務模型

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中包括全域性事務格式,標識,分支標識。

[img]http://dl.iteye.com/upload/attachment/0082/3143/053c788d-15f1-34d6-8bef-2884d9c13dc9.jpg[/img]


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