當業務處理耗時較長(如報表生成、郵件發送、資料同步)時,同步呼叫會阻塞客戶端。EJB 3 的訊息驅動 Bean (Message-Driven Bean, MDB) 結合 JMS 佇列,實現非同步處理架構。本文完整示範:實體 Bean 持久化、MDB 接收處理、Session Bean 發送訊息、獨立客戶端發送訊息的端到端流程。
客戶端 / Session Bean
│ 發送 ObjectMessage (包含實體 Bean)
▼
JMS Queue (MDBQueue)
│ 容器自動監聽
▼
Message-Driven Bean (onMessage)
│ 解析實體 → EntityManager.persist()
▼
資料庫持久化完成
@Entity
@Table(name="t_date")
public class DateBean implements Serializable {
private int id;
private Date myDate;
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
public int getId() { return id; }
public void setId(int id) { this.id = id; }
@Column(name="mydate")
public Date getMyDate() { return myDate; }
public void setMyDate(Date myDate) { this.myDate = myDate; }
}
@MessageDriven(
activationConfig = {
@ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="queue/MDBQueue")
})
public class DateMessageBean implements MessageListener {
@PersistenceContext(unitName="myentity1")
private EntityManager em;
@Override
public void onMessage(Message message) {
try {
if (message instanceof ObjectMessage) {
ObjectMessage objMsg = (ObjectMessage) message;
DateBean dateBean = (DateBean) objMsg.getObject();
em.persist(dateBean);
System.out.println("成功持久化 DateBean 物件!");
} else {
System.out.println("訊息類型錯誤!");
}
} catch (Exception e) {
throw new EJBException(e);
}
}
}
destinationType=javax.jms.Queue:使用點對點佇列模式destination=queue/MDBQueue:JNDI 名稱,需在 JBoss 手動建立對應 Queue在 $JBOSS_HOME/server/default/deploy 建立 mdb-service.xml(檔名須以 -service.xml 結尾):
<?xml version="1.0" encoding="UTF-8"?>
<server>
<mbean code="org.jboss.mq.server.jmx.Queue"
name="jboss.mq.destination:service=Queue,name=MDBQueue">
<depends optional-attribute-name="DestinationManager">
jboss.mq:service=DestinationManager
</depends>
</mbean>
</server>
注意:name 屬性中的 name=MDBQueue 必須與 queue/MDBQueue 斜線後一致,否則啟動拋出 NameNotFoundException: MDBQueue not bound。
@Stateless
public class GreeterBean implements Greeter {
@Resource(mappedName="ConnectionFactory")
private ConnectionFactory cf;
@Resource(mappedName="queue/MDBQueue")
private Queue queue;
@Override
public String greet(String message) {
try {
DateBean db = new DateBean();
db.setMyDate(new Date());
Connection conn = cf.createConnection();
Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(queue);
ObjectMessage objMsg = session.createObjectMessage();
objMsg.setObject(db);
producer.send(objMsg);
conn.close();
System.out.println("成功發送訊息!");
} catch (Exception e) {
System.out.println("發送訊息失敗!");
}
return "方法成功返回";
}
}
public class Client {
public static void main(String[] args) throws Exception {
InitialContext ctx = new InitialContext();
QueueConnectionFactory factory = (QueueConnectionFactory) ctx.lookup("ConnectionFactory");
QueueConnection conn = factory.createQueueConnection();
QueueSession session = conn.createQueueSession(false, QueueSession.AUTO_ACKNOWLEDGE);
Destination dest = (Queue) ctx.lookup("queue/MDBQueue");
MessageProducer producer = session.createProducer(dest);
ObjectMessage objMsg = session.createObjectMessage();
DateBean db = new DateBean();
db.setMyDate(new Date());
objMsg.setObject(db);
producer.send(objMsg);
conn.close();
System.out.println("成功發送訊息!");
}
}
| 錯誤現象 | 原因 | 解決 |
|---|---|---|
NameNotFoundException: MDBQueue not bound | 未建立 Queue 或 JNDI 名稱不匹配 | 依上方 mdb-service.xml 建立 Queue,確認 name 屬性 |
Required config property 'destination' not found | @MessageDriven 缺少 destination 設定 | 補上 activationConfig 的 destination 屬性 |
| 訊息收不到 | ObjectMessage 類別未實作 Serializable | 實體 Bean 必須 implements Serializable |
@ActivationConfigProperty(propertyName="maxSession", propertyValue="10"))onMessage 拋出異常時,訊息可重新投遞(需配置 Redelivery)