使用 CMT(Container-Managed Transaction)


要在Bean上使用CMT,基本上,就是在方法上使用@TransactionAttribute設定交易屬性,例如:
  • HelloBean.java
package onlyfun.caterpillar;

import javax.ejb.Local;

@Local
public interface HelloBean {
public void demoTransaction1(String message);
public void demoTransaction2(String message);
}

  • HelloBeanImpl.java
package onlyfun.caterpillar;

import java.util.logging.*;
import javax.annotation.*;
import javax.ejb.*;
import javax.jms.*;

@Stateless
public class HelloBeanImpl implements HelloBean {
@Resource(name="jms/HelloQueueFactory")
private ConnectionFactory connectionFactory;

@Resource(name="jms/HelloQueue")
private Destination queue;

@Resource
private SessionContext context;

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void demoTransaction1(String message) {
try {
sendMessage(message);
} catch (JMSException ex) {
Logger.getLogger(
HelloBeanImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void demoTransaction2(String message) {
try {
sendMessage(message);
context.setRollbackOnly();
} catch (JMSException ex) {
Logger.getLogger(
HelloBeanImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}

private void sendMessage(String message) throws JMSException {
Connection connection = connectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);
TextMessage textMessage = session.createTextMessage();
textMessage.setText(message + "processed....");
producer.send(textMessage);

producer.close();
session.close();
connection.close();
}
}

這個Stateless Session Bean示範了兩個方法,皆設定交易屬性為REQUIRED,Bean預設的交易為CMT,您無需特別設定,若要明確設定,則使用@TRansactionManagement標註如下:
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class HelloBeanImpl implements HelloBean {
    ....
}

根據設定,在呼叫方法時,會啟始一個交易,若無法完成交易,則會撤回操作,程式中的demoTransaction2()則示 範了,如何透過SessionContext的setRollbackOnly()方法設定撤回標記,設定了撤回標記,方法呼叫結束後,容器將撤回交易,您可以使用getRollbackOnly()方法來判斷交易撤回是否被設定,您也可以丟出javax.transaction.SystemException,由容器退回交易。

通常,您會在方法呼叫中捕捉到例外時,呼叫setRollbackOnly()方法設定撤回標記,例如:
...
try {
    ...
}
catch(SomeException ex) {
    context.setRollbackOnly();
}
catch(OtherException ex) {
    context.setRollbackOnly();
}
....

您也可以在自訂例外類別時,使用@ApplicationException(rollback=true)設定,當丟出例外時,設定撤回標記,這在之後說明例外處理時還會介紹。

setRollbackOnly()方法可以在REQUIRED、REQUIRED_NEW與MANDATORY設定時呼叫,否則會丟出IllegalStateException。

上面這個範例,搭配 TimerService 與 Timer 回呼 中的Message-Driven Bean,若呼叫demoTransaction1(),則Message-Driven Bean會收到訊息加以處理,若呼叫demoTransaction2(),則訊息不會傳給Message-Driven Bean。

在訊息服務中使用交易時常見的誤會是:「認為交易包括的產生者的訊息傳送、消耗者的接收、回覆。」事實上,交易是無法從產生者傳遞至接收者,訊息服務中若要使用交易,是訊息產生者傳送訊息至MOM是一個交易,而訊息消耗者從MOM接收訊息是另一個交易。

您無法傳遞交易環境給Message-Driven Bean的onMessage()方法,若您要在onMessage()上使用CMT,則可以設定的屬性是REQUIRED、NOT_SUPPORTED,Message-Driven Bean消耗訊息回覆確認之後才算完成交易,若交易失敗,則訊息不會被消耗,訊息仍被放在佇列上,回復至交易之前的狀態。

在Timer服務時,Timeout的回呼方法若要使用交易,可使用REQUIRED或REQUIRED_NEW,若交易被撤回,則Timeout方法會被重新呼叫。

容器會負責撤回資源系統的變更(例如資料庫上的欄位變更、MOM上的訊息),但不負責撤回Stateful Session Bean的成員變數,所以必須考慮到,當交易作用於Stateful Session Bean上,在交易撤回時,Stateful Session Bean的成員變數是否需回復至交易開始前的狀態,若這是必要的,則您可以實作SessionSynchronization介面:
public interface SessionSynchronization {
   void afterBegin() throws EJBException, RemoteException;
   void beforeCompletion() throws EJBException, RemoteException;
   void afterCompletion(boolean committed)throws EJBException,RemoteException;
}

afterBegin()會在交易開始之後呼叫,beforeCompletion()會在交易完成或撤回前呼叫, afterCompletion()會在交易完成或撤回後呼叫,若其中committed為true,表示交易成功,false表示交易撤回,這是您可以 回復Stateful Session Bean成員變數的地方。