在本系列先前的文章中,我們主要講解了JDBC對本地事務的處理,本篇文章將講到一個分布式事務的例子。
請通過以下方式下載github源代碼:
本地事務和分布式事務的區別在于:本地事務只用于處理單一數據源事務(比如單個數據庫),分布式事務可以處理多種異構的數據源,比如某個業務操作中同時包含了JDBC和JMS或者某個操作需要訪問多個不同的數據庫。
Java通過JTA完成分布式事務,JTA本身只是一種規范,不同的應用服務器都包含有自己的實現(比如JbossJTA),同時還存在獨立于應用服務器的單獨JTA實現,比如本篇中要講到的Atomikos。對于JTA的原理,這里不細講,讀者可以通過這篇文章了解相關知識。
在本篇文章中,我們將實現以下一個應用場景:你在網上購物,下了訂單之后,訂單數據將保存在系統的數據庫中,同時為了安排物流,訂單信息將以消息(Message)的方式發送到物流部門以便送貨。
以上操作同時設計到數據庫操作和JMS消息發送,為了使整個操作成為一個原子操作,我們只能選擇分布式事務。我們首先設計一個service層,定義OrderService接口:
packagedavenkin;public interfaceOrderService {public voidmakeOrder(Order order);
}
為了簡單起見,我們設計一個非常簡單的領域對象Order:
@XmlRootElement(name = "Order")
@XmlAccessorType(XmlAccessType.FIELD)public classOrder {
@XmlElement(name= "Id",required = true)private longid;
@XmlElement(name= "ItemName",required = true)privateString itemName;
@XmlElement(name= "Price",required = true)private doubleprice;
@XmlElement(name= "BuyerName",required = true)privateString buyerName;
@XmlElement(name= "MailAddress",required = true)privateString mailAddress;publicOrder() {
}
為了采用JAXB對Order對象進行Marshal和Unmarshal,我們在Order類中加入了JAXB相關的Annotation。 我們將使用Hibernate來完成數據持久化,然后使用Spring提供的JmsTemplate將Order轉成xml后以TextMessage的形式發送到物流部門的ORDER.QUEUE中。
(一)準備數據庫
為了方便,我們將采用Spring提供的embedded數據庫,默認情況下Spring采用HSQL作為后臺數據庫,雖然在本例中我們將采用HSQL的非XA的DataSource,但是通過Atomikos包裝之后依然可以參與分布式事務。
SQL腳本包含在createDB.sql文件中:
CREATE TABLEUSER_ORDER(
IDINT NOT NULL,
ITEM_NAMEVARCHAR (100) NOT NULL UNIQUE,
PRICEDOUBLE NOT NULL,
BUYER_NAMECHAR (32) NOT NULL,
MAIL_ADDRESSVARCHAR(500) NOT NULL,PRIMARY KEY(ID)
);
在Spring中配置DataSource如下:
(二)啟動ActiveMQ
我們將采用embedded的ActiveMQ,在測試之前啟動ActiveMQ提供的BrokerService,在測試執行完之后關閉BrokerService。
@BeforeClasspublic static void startEmbeddedActiveMq() throwsException {
broker= newBrokerService();
broker.addConnector("tcp://localhost:61616");
broker.start();
}
@AfterClasspublic static void stopEmbeddedActiveMq() throwsException {
broker.stop();
}
(三)實現OrderService
創建一個DefaultOrderService,該類實現了OrderService接口,并維護一個JmsTemplate和一個Hibernate的SessionFactory實例變量,分別用于Message的發送和數據庫處理。
packagedavenkin;importorg.hibernate.SessionFactory;importorg.hibernate.classic.Session;importorg.springframework.beans.factory.annotation.Required;importorg.springframework.jms.core.JmsTemplate;importorg.springframework.transaction.annotation.Transactional;public class DefaultOrderService implementsOrderService{privateJmsTemplate jmsTemplate;privateSessionFactory sessionFactory;
@Override
@Transactionalpublic voidmakeOrder(Order order) {
Session session=sessionFactory.getCurrentSession();
session.save(order);
jmsTemplate.convertAndSend(order);
}
@Requiredpublic voidsetJmsTemplate(JmsTemplate jmsTemplate) {this.jmsTemplate =jmsTemplate;
}
@Requiredpublic voidsetSessionFactory(SessionFactory sessionFactory) {this.sessionFactory =sessionFactory;
}
}
(四)創建Order的Mapping配置文件
/p>
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
(五)配置Atomikos事務
在Spring的IoC容器中,我們需要配置由Atomikos提供的UserTransaction和TransactionManager,然后再配置Spring的JtaTransactionManager:
com.atomikos.icatch.standalone.UserTransactionServiceFactory
(六)配置JMS
對于JMS,為了能使ActiveMQ加入到分布式事務中,我們需要配置ActiveMQXAConnectionFactory,而不是ActiveMQConnectionFactory,然后再配置JmsTemplate,此外還需要配置MessageConvertor在Order對象和XML之間互轉。
(七)測試
在測試中,我們首先通過(二)中的方法啟動ActiveMQ,再調用DefaultOrderService,最后對數據庫和QUEUE進行驗證:
@Testpublic voidmakeOrder(){
orderService.makeOrder(createOrder());
JdbcTemplate jdbcTemplate= newJdbcTemplate(dataSource);
assertEquals(1, jdbcTemplate.queryForInt("SELECT COUNT(*) FROM USER_ORDER"));
String dbItemName= jdbcTemplate.queryForObject("SELECT ITEM_NAME FROM USER_ORDER", String.class);
String messageItemName=((Order) jmsTemplate.receiveAndConvert()).getItemName();
assertEquals(dbItemName, messageItemName);
}
@Test(expected= IllegalArgumentException.class)public voidfailToMakeOrder()
{
orderService.makeOrder(null);
JdbcTemplate jdbcTemplate= newJdbcTemplate(dataSource);
assertEquals(0, jdbcTemplate.queryForInt("SELECT COUNT(*) FROM USER_ORDER"));
assertNull(jmsTemplate.receiveAndConvert());
}