java事务全解析(八)--分布式事务入门例子(Spring+JTA+Atomikos+Hibernate+JMS

来源:互联网 发布:怎么开通淘宝借贷宝 编辑:程序博客网 时间:2024/05/16 14:51

在本系列先前的文章中,我们主要讲解了JDBC对本地事务的处理,本篇文章将讲到一个分布式事务的例子。

 

请通过以下方式下载github源代码:

git clone https://github.com/davenkin/jta-atomikos-hibernate-activemq.git

 

本地事务和分布式事务的区别在于:本地事务只用于处理单一数据源事务(比如单个数据库),分布式事务可以处理多种异构的数据源,比如某个业务操作中同时包含了JDBC和JMS或者某个操作需要访问多个不同的数据库。

 

Java通过JTA完成分布式事务,JTA本身只是一种规范,不同的应用服务器都包含有自己的实现(比如JbossJTA),同时还存在独立于应用服务器的单独JTA实现,比如本篇中要讲到的Atomikos。对于JTA的原理,这里不细讲,读者可以通过这篇文章了解相关知识。

 

在本篇文章中,我们将实现以下一个应用场景:你在网上购物,下了订单之后,订单数据将保存在系统的数据库中,同时为了安排物流,订单信息将以消息(Message)的方式发送到物流部门以便送货。

 

以上操作同时设计到数据库操作和JMS消息发送,为了使整个操作成为一个原子操作,我们只能选择分布式事务。我们首先设计一个service层,定义OrderService接口:

[java] view plain copyprint?
  1. public interface OrderService {  
  2.                           
  3.     public void makeOrder(Order order);  
  4. }  
为了简单起见,我们设计一个非常简单的领域对象Order:
[java] view plain copyprint?
  1. @XmlRootElement(name = "Order")  
  2. @XmlAccessorType(XmlAccessType.FIELD)  
  3. public class Order {  
  4.                         
  5.     @XmlElement(name = "Id",required = true)  
  6.     private long id;  
  7.                         
  8.     @XmlElement(name = "ItemName",required = true)  
  9.     private String itemName;  
  10.                         
  11.     @XmlElement(name = "Price",required = true)  
  12.     private double price;  
  13.                         
  14.     @XmlElement(name = "BuyerName",required = true)  
  15.     private String buyerName;  
  16.                         
  17.     @XmlElement(name = "MailAddress",required = true)  
  18.     private String mailAddress;  
  19.                         
  20.     public Order() {  
  21.     }  

为了采用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 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如下:

[html] view plain copyprint?
  1. <jdbc:embedded-database id="dataSource">  
  2.         <jdbc:script location="classpath:createDB.sql"/>  
  3.     </jdbc:embedded-database>  

(二)启动ActiveMQ

我们将采用embedded的ActiveMQ,在测试之前启动ActiveMQ提供的BrokerService,在测试执行完之后关闭BrokerService。


[java] view plain copyprint?
  1. @BeforeClass  
  2.     public static void startEmbeddedActiveMq() throws Exception {  
  3.         broker = new BrokerService();  
  4.         broker.addConnector("tcp://localhost:61616");  
  5.         broker.start();  
  6.     }  
  7.                    
  8.     @AfterClass  
  9.     public static void stopEmbeddedActiveMq() throws Exception {  
  10.         broker.stop();  
  11.     }  

(三)实现OrderService

创建一个DefaultOrderService,该类实现了OrderService接口,并维护一个JmsTemplate和一个Hibernate的SessionFactory实例变量,分别用于Message的发送和数据库处理。

[java] view plain copyprint?
  1. public class DefaultOrderService  implements OrderService{  
  2.     private JmsTemplate jmsTemplate;  
  3.     private SessionFactory sessionFactory;  
  4.                  
  5.     @Override  
  6.     @Transactional  
  7.     public void makeOrder(Order order) {  
  8.         Session session = sessionFactory.getCurrentSession();  
  9.         session.save(order);  
  10.         jmsTemplate.convertAndSend(order);  
  11.                  
  12.     }  
  13.                  
  14.     @Required  
  15.     public void setJmsTemplate(JmsTemplate jmsTemplate) {  
  16.         this.jmsTemplate = jmsTemplate;  
  17.     }  
  18.                  
  19.     @Required  
  20.     public void setSessionFactory(SessionFactory sessionFactory) {  
  21.         this.sessionFactory = sessionFactory;  
  22.     }  
  23. }  

(四)创建Order的Mapping配置文件
[html] view plain copyprint?
  1. <?xml version="1.0"?>  
  2. <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
  3.         "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  4.                
  5. <hibernate-mapping>  
  6.     <class name="davenkin.Order" table="USER_ORDER">  
  7.         <id name="id" type="long">  
  8.             <column name="ID" />  
  9.             <generator class="increment" />  
  10.         </id>  
  11.         <property name="itemName" type="string">  
  12.             <column name="ITEM_NAME" />  
  13.         </property>  
  14.         <property name="price" type="double">  
  15.             <column name="PRICE"/>  
  16.         </property>  
  17.         <property name="buyerName" type="string">  
  18.             <column name="BUYER_NAME"/>  
  19.         </property>  
  20.         <property name="mailAddress" type="string">  
  21.             <column name="MAIL_ADDRESS"/>  
  22.         </property>  
  23.     </class>  
  24. </hibernate-mapping>  

(五)配置Atomikos事务

在Spring的IoC容器中,我们需要配置由Atomikos提供的UserTransaction和TransactionManager,然后再配置Spring的JtaTransactionManager:


[html] view plain copyprint?
  1. <bean id="userTransactionService" class="com.atomikos.icatch.config.UserTransactionServiceImp" init-method="init" destroy-method="shutdownForce">  
  2.         <constructor-arg>  
  3.             <props>  
  4.                 <prop key="com.atomikos.icatch.service">com.atomikos.icatch.standalone.UserTransactionServiceFactory</prop>  
  5.             </props>  
  6.         </constructor-arg>  
  7.     </bean>  
  8.               
  9.     <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close" depends-on="userTransactionService">  
  10.         <property name="forceShutdown" value="false" />  
  11.     </bean>  
  12.               
  13.     <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp" depends-on="userTransactionService">  
  14.         <property name="transactionTimeout" value="300" />  
  15.     </bean>  
  16.               
  17.     <bean id="jtaTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager" depends-on="userTransactionService">  
  18.         <property name="transactionManager" ref="atomikosTransactionManager" />  
  19.         <property name="userTransaction" ref="atomikosUserTransaction" />  
  20.     </bean>  
  21.               
  22.     <tx:annotation-driven transaction-manager="jtaTransactionManager" />  

(六)配置JMS

对于JMS,为了能使ActiveMQ加入到分布式事务中,我们需要配置ActiveMQXAConnectionFactory,而不是ActiveMQConnectionFactory,然后再配置JmsTemplate,此外还需要配置MessageConvertor在Order对象和XML之间互转。


[html] view plain copyprint?
  1. <bean id="jmsXaConnectionFactory" class="org.apache.activemq.ActiveMQXAConnectionFactory">  
  2.         <property name="brokerURL" value="tcp://localhost:61616" />  
  3.     </bean>  
  4.              
  5.     <bean id="amqConnectionFactory" class="com.atomikos.jms.AtomikosConnectionFactoryBean" init-method="init">  
  6.         <property name="uniqueResourceName" value="XAactiveMQ" />  
  7.         <property name="xaConnectionFactory" ref="jmsXaConnectionFactory" />  
  8.         <property name="poolSize" value="5"/>  
  9.     </bean>  
  10.              
  11.              
  12.     <bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">  
  13.         <property name="connectionFactory" ref="amqConnectionFactory"/>  
  14.         <property name="receiveTimeout" value="2000" />  
  15.         <property name="defaultDestination" ref="orderQueue"/>  
  16.         <property name="sessionTransacted" value="true" />  
  17.         <property name="messageConverter" ref="oxmMessageConverter"/>  
  18.     </bean>  
  19.              
  20.     <bean id="orderQueue" class="org.apache.activemq.command.ActiveMQQueue">  
  21.         <constructor-arg value="ORDER.QUEUE"/>  
  22.     </bean>  
  23.              
  24.              
  25.     <bean id="oxmMessageConverter"  
  26.           class="org.springframework.jms.support.converter.MarshallingMessageConverter">  
  27.         <property name="marshaller" ref="marshaller"/>  
  28.         <property name="unmarshaller" ref="marshaller"/>  
  29.     </bean>  
  30.              
  31.     <oxm:jaxb2-marshaller id="marshaller">  
  32.         <oxm:class-to-be-bound name="davenkin.Order"/>  
  33.     </oxm:jaxb2-marshaller>  

(七)测试

在测试中,我们首先通过(二)中的方法启动ActiveMQ,再调用DefaultOrderService,最后对数据库和QUEUE进行验证:



[java] view plain copyprint?
  1. @Test  
  2.     public void makeOrder(){  
  3.         orderService.makeOrder(createOrder());  
  4.         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);  
  5.         assertEquals(1, jdbcTemplate.queryForInt("SELECT COUNT(*) FROM USER_ORDER"));  
  6.         String dbItemName = jdbcTemplate.queryForObject("SELECT ITEM_NAME FROM USER_ORDER", String.class);  
  7.         String messageItemName = ((Order) jmsTemplate.receiveAndConvert()).getItemName();  
  8.         assertEquals(dbItemName, messageItemName);  
  9.     }  
  10.             
  11.     @Test(expected = IllegalArgumentException.class)  
  12.     public void failToMakeOrder()  
  13.     {  
  14.         orderService.makeOrder(null);  
  15.         JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);  
  16.         assertEquals(0, jdbcTemplate.queryForInt("SELECT COUNT(*) FROM USER_ORDER"));  
  17.         assertNull(jmsTemplate.receiveAndConvert());  
  18.     }  




阅读全文
0 0
原创粉丝点击