Java事務処理の全解析(8)——分散式事務(Spring+JTA+Atomikos+Hbernate+JMS)
9339 ワード
このシリーズの前の文章では、JDBCの現地事務に対する処理について説明しました。この文章では分散的な事務の例を紹介します。
以下の方法でgithubソースコードをダウンロードしてください。
JavaはJTAを通じて分散事務を完成しました。JTA自体は標準的なもので、アプリケーションサーバによって自分の実現(例えばJbossJTA)が含まれています。JTAの原理については、ここで詳しく説明しないと、読者はこの文章を通じて関連知識を知ることができます。
本記事では、オンラインショッピングで注文した後、注文データをシステムのデータベースに保存し、物流を手配するために、注文情報をメッセージで送ります。
以上の操作は同時にデータベース操作とJMSメッセージ送信に設計されています。全体の動作を原子操作にするためには、分散式事務を選択するしかないです。まずservice層を設計し、Order Serviceインターフェースを定義します。
(一)データベースの準備
便利さのためにSpringで提供するembeddedデータベースを採用します。デフォルトではSpringはHSQLをバックグラウンドデータベースとして採用しています。本例ではHSQLの非XAのDataSourceを採用しますが、Atomikosパッケージを通しても分散事務に参加できます。
SQLスクリプトはcreateDB.sqlファイルに含まれています。
embeddedのActiveMQを して、テストの にActiveMQから されたBrokerServiceを して、テストの が わったらBroker Serviceを じます。
つのDefaultOrderServiceを して、このクラスはOrder Serviceインターフェースを して、 つのJmsTemplateと つのHVarnateのSession Factoryインスタンス を して、それぞれMessageの とデータベース に します。
SpringのIoC には、Atomikosから されるUserTransationとTransation Managerを してからSpringのJtaTransactionagerを する があります。
JMSに しては、ActiveMQを に させるために、ActiveMQAConnectoryを する があります。Active MQConnection Factoryではなく、JmsTemplateを する があります。また、Message CovertorをOrderオブジェクトとXMLの に する があります。
テストでは、まず( )の でActiveMQを し、DefaultOrder Serviceを び して、 にデータベースとQUUEを します。
以下の方法でgithubソースコードをダウンロードしてください。
git clone https://github.com/davenkin/jta-atomikos-hibernate-activemq.git
ローカル事務と分散事務の違いは、ローカル事務は単一のデータソーストランザクション(例えば、単一のデータベース)を処理するためにのみ使用され、分散事務は複数の異性のデータソースを処理することができます。例えば、ある業務操作には、JDBCとJMSまたはある操作が複数の異なるデータベースにアクセスする必要があります。JavaはJTAを通じて分散事務を完成しました。JTA自体は標準的なもので、アプリケーションサーバによって自分の実現(例えばJbossJTA)が含まれています。JTAの原理については、ここで詳しく説明しないと、読者はこの文章を通じて関連知識を知ることができます。
本記事では、オンラインショッピングで注文した後、注文データをシステムのデータベースに保存し、物流を手配するために、注文情報をメッセージで送ります。
以上の操作は同時にデータベース操作とJMSメッセージ送信に設計されています。全体の動作を原子操作にするためには、分散式事務を選択するしかないです。まずservice層を設計し、Order Serviceインターフェースを定義します。
public interface OrderService {
public void makeOrder(Order order);
}
簡単のために、非常に簡単な領域オブジェクトOrderを設計します。@XmlRootElement(name = "Order")
@XmlAccessorType(XmlAccessType.FIELD)
public class Order {
@XmlElement(name = "Id",required = true)
private long id;
@XmlElement(name = "ItemName",required = true)
private String itemName;
@XmlElement(name = "Price",required = true)
private double price;
@XmlElement(name = "BuyerName",required = true)
private String buyerName;
@XmlElement(name = "MailAddress",required = true)
private String mailAddress;
public Order() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getBuyerName() {
return buyerName;
}
public void setBuyerName(String buyerName) {
this.buyerName = buyerName;
}
public String getMailAddress() {
return mailAddress;
}
public void setMailAddress(String mailAddress) {
this.mailAddress = mailAddress;
}
}
JAXBを採用して、Orderの対象に対してMasharlとUnimasharlを行うために、私達はOrder類にJAXB関連のAnnotationを入れました。私たちはヒップホップを使用してデータの持続化を完成し、Springで提供されたJmsTemplateを使用してOrderをxmlに変換して、TextMessageとして物流部門のORDER.QUUEに送信します。(一)データベースの準備
便利さのためにSpringで提供するembeddedデータベースを採用します。デフォルトではSpringはHSQLをバックグラウンドデータベースとして採用しています。本例ではHSQLの非XAのDataSourceを採用しますが、Atomikosパッケージを通しても分散事務に参加できます。
SQLスクリプトはcreateDB.sqlファイルに含まれています。
CREATE TABLE USER_ORDER(
ID INT NOT NULL,
ITEM_NAME VARCHAR (100) NOT NULL UNIQUE,
PRICE DOUBLE NOT NULL,
BUYER_NAME CHAR (32) NOT NULL,
MAIL_ADDRESS VARCHAR(500) NOT NULL,
PRIMARY KEY(ID)
);
SpringにDataSourceを配置すると以下のようになります。
( )ActiveMQの embeddedのActiveMQを して、テストの にActiveMQから されたBrokerServiceを して、テストの が わったらBroker Serviceを じます。
@BeforeClass
public static void startEmbeddedActiveMq() throws Exception {
broker = new BrokerService();
broker.addConnector("tcp://localhost:61616");
broker.start();
}
@AfterClass
public static void stopEmbeddedActiveMq() throws Exception {
broker.stop();
}
( )Order Serviceの つのDefaultOrderServiceを して、このクラスはOrder Serviceインターフェースを して、 つのJmsTemplateと つのHVarnateのSession Factoryインスタンス を して、それぞれMessageの とデータベース に します。
public class DefaultOrderService implements OrderService{
private JmsTemplate jmsTemplate;
private SessionFactory sessionFactory;
@Override
@Transactional
public void makeOrder(Order order) {
Session session = sessionFactory.getCurrentSession();
jmsTemplate.convertAndSend(order);
session.save(order);
}
@Required
public void setJmsTemplate(JmsTemplate jmsTemplate) {
this.jmsTemplate = jmsTemplate;
}
@Required
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
}
( )OrderのMappingプロファイルを する
( )Atomikos の SpringのIoC には、Atomikosから されるUserTransationとTransation Managerを してからSpringのJtaTransactionagerを する があります。
com.atomikos.icatch.standalone.UserTransactionServiceFactory
( )JMSの JMSに しては、ActiveMQを に させるために、ActiveMQAConnectoryを する があります。Active MQConnection Factoryではなく、JmsTemplateを する があります。また、Message CovertorをOrderオブジェクトとXMLの に する があります。
( )テストテストでは、まず( )の でActiveMQを し、DefaultOrder Serviceを び して、 にデータベースとQUUEを します。
@Test
public void makeOrder() {
orderService.makeOrder(createOrder());
JdbcTemplate jdbcTemplate = new JdbcTemplate(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 void failToMakeOrder() {
orderService.makeOrder(null);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
assertEquals(0, jdbcTemplate.queryForInt("SELECT COUNT(*) FROM USER_ORDER"));
assertNull(jmsTemplate.receiveAndConvert());
}
private Order createOrder() {
Order order = new Order();
order.setBuyerName("davenkin");
order.setItemName(randomName());
order.setMailAddress("chengdu");
order.setPrice(randomPrice());
return order;
}
private String randomName() {
return "book" + System.currentTimeMillis();
}
private double randomPrice() {
String randomTimeString = String.valueOf(System.currentTimeMillis());
return Double.parseDouble(randomTimeString.substring(randomTimeString.length() - 3));
}