Working with Transactions Over Multiple Transactional Resources

So far, we have only concerned ourselves with transactions in the Data Access Tier. In a large enterprise application, transactions spread across multiple components, and if the application is to be robust, you cannot simply ignore this and implement local transactional blocks and hope that the overall business process succeeds. If you need to enlist operations that manipulate the database, message queue, and any additional transactional resource in one transaction, you are left with the JTA transaction manager.

JTA is the only way to support global transactions. This implies that you need a J2EE application server to run an application with global transactions. Fortunately, this is usually the case because enterprise applications often use JMS, EJBs, and JNDI, which are all implemented by the application server.

The example we show you in this section demonstrates how to use JTA transactions across a database and a message queue. In this example, we create a simple web application, which sends a message to a queue. The message is then picked up by an MDB, which notifies us that it was called. Finally, the JTAAccountManager.insert() method throws a RuntimeException in about 50 percent of the cases. This exception should then cause the message to be removed from the queue and the database work to be rolled back. If this is the case, the MDB never gets called. We are not going to discuss how to create message-driven beans in Spring in great detail; if you are interested in EJB development using Spring, read Chapter 13.

The sample application we work with in this section requires a JBoss application server and an XA-capable JDBC driver. For this example, we chose the Oracle 10g database and its JDBC driver. Because it is the most complex sample in this chapter, let's begin with a UML diagram (Figure 12-2) that highlights the key components of the sample.

image from book
Figure 12-2: Components of the JTA example

JTAAccountManager is the actual implementation of the application's business logic; the EJB container is only used to host the message-driven bean. The entire application is packaged in an EAR file with two modules: an EJB JAR file containing the message-driven bean and a web module with the testing web application.

Unfortunately, as you will see in the following more detailed discussion, this design means that we have to split the Spring context files into several files because not all beans are needed and available in the EJB tier. We kept the applicationContext.xml file with the DAO definitions, but we moved the dataSource bean definition using JNDI data sources into the applicationContext-as.xml file. Finally, applicationContext-jms.xml defines the queueConnectionFactory, springQueue, and accountManagerJTATarget beans. Both applicationContext-as.xml and applicationContextjms.xml can only be used in a J2EE application server. Figure 12-3 shows the source directory structure and sheds some light on how the application is going to be packaged.

image from book
Figure 12-3: Source directory structure

Implementing a Message-Driven Bean

Let us begin the source code discussion by looking at the MDB implementation (see Listing 12-21). It contains the "almost empty" applicationContext.xml file in ch12/src/ejb, which is a simple placeholder for the EJB JAR. Spring EJB support needs to build a Spring context to make it available in the EJBs. Because we do not use any Spring beans in the MDB, we can leave the file almost empty, we just need to make sure it is a valid XML according to the DTD. As you just saw, Figure 12-3 shows how the source files are structured and hopefully gives an idea of how the EAR file is going to be built.

Listing 12-21: SpringMDB Implementation

image from book
package com.apress.prospring.ch12.ejb;      import javax.jms.Message;      import org.springframework.ejb.support.AbstractJmsMessageDrivenBean;      public class SpringMDB extends AbstractJmsMessageDrivenBean {          protected void onEjbCreate() {         // noop              }          public void onMessage(Message message) {         System.out.println("Message received");     }   }
image from book

As you can see from the code, the MDB does not even examine the message, it simply prints "Message received" to standard output. This MDB is packaged in spring-ch12-ejb.jar, along with its null Spring context file and ejb-jar.xml and jboss.xml descriptor files (see Listing 12-22).

Listing 12-22: EJB JAR Descriptor Files

image from book
// applicationContext.xml packaged in the root of the jar <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean /> </beans>      // ejb-jar.xml in /META-INF <?xml version="1.0"?>      <!DOCTYPE ejb-jar PUBLIC      "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0// EN"      "http://java.sun.com/dtd/ejb-jar_2_0.dtd" > <ejb-jar>     <enterprise-beans>         <message-driven>              <ejb-name>SpringMDB</ejb-name>              <ejb-class>com.apress.prospring.ch12.ejb.SpringMDB</ejb-class>              <transaction-type>Container</transaction-type>              <acknowledge-mode>AUTO_ACKNOWLEDGE</acknowledge-mode>              <message-driven-destination>                  <destination-type>javax.jms.Queue</destination-type>              </message-driven-destination>              <env-entry>                 <env-entry-name>ejb/BeanFactoryPath</env-entry-name>                 <env-entry-type>java.lang.String</env-entry-type>                 <env-entry-value>applicationContext.xml</env-entry-value>             </env-entry>             <resource-ref>                  <description></description>                 <res-ref-name>jms/SpringMessages</res-ref-name>                  <res-type>javax.jms.XAQueueConnectionFactory</res-type>                  <res-auth>Container</res-auth>              </resource-ref>          </message-driven>     </enterprise-beans> </ejb-jar>      // jboss.xml in /META-INF <?xml version="1.0"?> <jboss>     <enterprise-beans>         <message-driven>             <ejb-name>SpringMDB</ejb-name>             <destination-jndi-name>queue/springQueue</destination-jndi-name>             <resource-ref>                 <res-ref-name>jms/SpringMessages</res-ref-name>                 <jndi-name>XAConnectionFactory</jndi-name>             </resource-ref>         </message-driven>     </enterprise-beans> </jboss> 
image from book

These EJB descriptor files map the MDB implementation in the SpringMDB class to an EJB named SpringMDB, which uses the Container transaction type, references the jms/SpringMessages queue, and has an env-entry of ejb/BeanFactoryPath that references the null Spring context file. Notice that the connection factory for the queue must be XAQueueConnectionFactory because this is the only factory that takes part in JTA transactions. Also, <jndi-name> in the <resource-ref> element in jboss.xml must be XAConnectionFactory because other implementations of the ConnectionFactory cannot participate in JTA transactions.

Using the Web Application

The web application uses the DAO classes you have seen working in the previous paragraphs, so there is no need to revisit them. Because the web application actually performs the database work and because it uses JTA transaction manager, we need to define the data source as a JNDI resource. To do this, we cerate a spring-xa-ds.xml[1] file (Listing 12-23) that references an XA JDBC data source.

Listing 12-23: spring-xa-ds.xml Data Source Definition

image from book
<?xml version="1.0" encoding="UTF-8"?>      <datasources>     <xa-datasource>         <jndi-name>XASpringDS</jndi-name>         <track-connection-by-tx/>         <isSameRM-override-value>false</isSameRM-override-value>         <xa-datasource-class>             oracle.jdbc.xa.client.OracleXADataSource</xa-datasource-class>         <xa-datasource-property name="URL">             jdbc:oracle:oci8:@orcl</xa-datasource-property>         <xa-datasource-property name="User">SCOTT</xa-datasource-property>         <xa-datasource-property name="Password">****</xa-datasource-property>         <exception-sorter-class-name>             org.jboss.resource.adapter.jdbc.vendor.OracleExceptionSorter         </exception-sorter-class-name>         <no-tx-separate-pools/>     </xa-datasource>     <mbean          code="org.jboss.resource.adapter.jdbc.xa.oracle.OracleXAExceptionFormatter"         name="jboss.jca:service=OracleXAExceptionFormatter">         <depends optional-attribute-name="TransactionManagerService">             jboss:service=TransactionManager</depends>     </mbean> </datasources> 
image from book

The spring-xa-ds.xml file defines a JNDI data source and must be deployed in the standard JBoss deploy directory.

Note 

The driver for your database must support the XA protocol, or the J2EE application server must provide an XA wrapper around the standard JDBC driver. Without XA, the database connection cannot be enlisted in global transactions. Only large and mostly commercial databases support XA natively in their JDBC drivers. We used the Oracle 10g database to run the example in this chapter. You can download Oracle 10g from www.oracle.com/database/index.html.

In Listing 12-24, the web application uses the message queue and the accountManager proxy references accountManagerJTATarget as its target object. The dataSource bean is configured to the JNDI resource. Here, we configure the JNDI references to the message queues in WEB-INF/jboss-web.xml and WEB-INF/web.xml.

Listing 12-24: WEB-INF Configuration Files

image from book
// jboss-web.xml <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE jboss-web     PUBLIC "-//JBoss//DTD Web Application 2.3V2//EN"     "http://www.jboss.org/j2ee/dtd/jboss-web_3_2.dtd">      <jboss-web>     <resource-ref>         <res-ref-name>queue/springQueue</res-ref-name>         <jndi-name>queue/springQueue</jndi-name>     </resource-ref> </jboss-web>      // web.xml <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC      "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"      "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app>     <display-name>Pro Spring Chapter 12</display-name>     <context-param>         <param-name>contextConfigLocation</param-name>         <param-value>WEB-INF/applicationContext.xml              WEB-INF/applicationContext-jms.xml             WEB-INF/applicationContext-as.xml</param-value>     </context-param>          <servlet>         <servlet-name>context</servlet-name>         <servlet-class>              org.springframework.web.context.ContextLoaderServlet</servlet-class>         <load-on-startup>1</load-on-startup>     </servlet>          <servlet>         <servlet-name>spring-ch12</servlet-name>         <servlet-class>             org.springframework.web.servlet.DispatcherServlet         </servlet-class>         <load-on-startup>2</load-on-startup>     </servlet>          <servlet-mapping>         <servlet-name>spring-ch12</servlet-name>         <url-pattern>*.html</url-pattern>     </servlet-mapping>          <resource-ref>         <res-ref-name>queue/springQueue</res-ref-name>         <res-type>javax.jms.Queue</res-type>         <res-auth>Container</res-auth>     </resource-ref> </web-app>
image from book

The jboss-web.xml descriptor file defines the JNDI resource mappings and the web.xml defines the Spring ContextLoaderServlet that loads the applicationContext files. The last configuration file, spring-ch12-servlet.xml, is another Spring context file that the DispatcherServlet loads, and it contains the definition for the TestController. We do not discuss web applications in Spring in this chapter; for more details go to

Listing 12-25: JTAAccountManager Implementation

image from book
package com.apress.prospring.ch12.business;      import java.math.BigDecimal;      import javax.jms.ConnectionFactory; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.Queue; import javax.jms.Session;      import org.springframework.beans.factory.BeanCreationException; import org.springframework.jms.core.JmsTemplate102; import org.springframework.jms.core.MessageCreator;      import com.apress.prospring.ch12.domain.Account;      public class JTAAccountManager extends AbstractAccountManager {          private ConnectionFactory connectionFactory;     private Queue queue;     private JmsTemplate102 jmsTemplate;          protected void initManager() {         if (connectionFactory == null)              throw new BeanCreationException("Must set connectionFactory");         if (queue == null)              throw new BeanCreationException("Must set queue");         jmsTemplate = new JmsTemplate102(connectionFactory, false);     }          public void insert(Account account) {                 doInsert(account);         jmsTemplate.send(queue, new MessageCreator() {                  public Message createMessage(Session session) throws JMSException {                 return session.createTextMessage("foobar");             }                      });               if (random.nextInt(10) > 5) {             System.out.println("Fail now");             throw new IllegalArgumentException("fff");         }     }          public void deposit(int accountId, BigDecimal amount) {         doDeposit(accountId, amount);     }          public void transfer(int sourceAccount, int targetAccount, BigDecimal amount) {         doTransfer(sourceAccount, targetAccount, amount);     }          public int count() {         return doCount();     }          public void setConnectionFactory(ConnectionFactory connectionFactory) {         this.connectionFactory = connectionFactory;     }          public void setQueue(Queue queue) {         this.queue = queue;     }     }
image from book

As you can see, we override the initManager() method to make sure the queue and connectionFactory properties are set. We also create an instance of JMSTemplate102 in this method. When the insert() method is executed, it calls the inherited doInsert() method and sends a message to the queue. The random generator adds a bit of unreliability to test that the transaction is indeed committed or rolled back.

In Listing 12-26, we define the accountManagerJTATarget bean in the applicationContextjms.xml file.

Listing 12-26: applicationContext-jms.xml Descriptor

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>     <bean           >         <property name="jndiName"><value>UIL2ConnectionFactory</value></property>     </bean>          <!-- Test queue -->     <bean  >         <property name="jndiName">             <value>java:comp/env/queue/springQueue</value></property>     </bean>          <bean bold">accountManagerJTATarget"          >         <property name="accountDao"><ref bean="accountDao"/></property>         <property name="historyDao"><ref bean="historyDao"/></property>         <property name="connectionFactory">             <ref bean="queueConnectionFactory"/></property>         <property name="queue"><ref bean="springQueue"/></property>     </bean>          </beans>  
image from book

To make the entire application use JTA transaction manager, we only have to define the correct transaction manager and set a reference to the transaction manager in the accountManager proxy (Listing 12-27).

Listing 12-27: JNDI dataSource and JTA Transction Manager Definition

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>          <!-- Data source bean -->          <bean           >         <property name="jndiName"><value>java:/XASpringDS</value></property>     </bean>          <bean           />          <bean           >         <property name="configLocation">             <value>/WEB-INF/classes/sqlMapConfig.xml</value></property>     </bean>      </beans>
image from book

The dataSource is no longer a BasicDataSource; it is up to the J2EE container to manage and pool the connections using its XA data source infrastructure. Now, the transaction manager is a JTATransactionManager and as such, it does not need a reference to the data source because it manages transactions across various transactional resources. The applicationContext.xml file remains unchanged as it only includes references to the transactionManager bean, not the bean itself.

The code in the TestController.handleRequest() method shown in Listing 12-28 is very simple because the controller does not know what implementation of AccountManager it is using; it simply tries to insert 100 accounts—you do not have to worry about the complex JTA transaction management.

Listing 12-28: TestController Implementation

image from book
package com.apress.prospring.ch12.web;      import java.math.BigDecimal;      import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;      import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.InitializingBean; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller;      import com.apress.prospring.ch12.business.AccountManager; import com.apress.prospring.ch12.domain.Account;      public class TestController implements InitializingBean, Controller {          private AccountManager accountManager;          public ModelAndView handleRequest(HttpServletRequest request,          HttpServletResponse response) throws Exception {              int count = accountManager.count();         int failures = 0;         int attempts = 100;         for (int i = 0; i < attempts; i++) {             Account account = new Account();             account.setBalance(new BigDecimal(0));             account.setNumber("" + System.currentTimeMillis());             account.setSortCode("dfgdfg");             try {                 accountManager.insert(account);             } catch (Exception ex) {                 failures++;             }         }                  response.getWriter().println("<br>Attempts:  " + attempts);         response.getWriter().println("<br>Commits:   " + (attempts - failures));         response.getWriter().println("<br>Rollbacks: " + failures);         response.getWriter().println("<hr>");         response.getWriter().println("<br>Old count: " + count +              ", new count " + accountManager.count());              return null;     }          public void afterPropertiesSet() throws Exception {         if (accountManager == null)              throw new BeanCreationException("Must set accountManager");     }          public void setAccountManager(AccountManager accountManager) {         this.accountManager = accountManager;     } }
image from book

If we now deploy the application and if JBoss is running on localhost, port 8090, we can go to http://localhost:8090/spring-ch12/test.html and see something like what is shown in Listing 12-29 in the browser.

Listing 12-29: Displayed test.html Page

image from book
Attempts: 100  Commits: 53  Rollbacks: 47       Old count: 0, new count 53
image from book

The number of commits you see here depends on the random generator and will probably be slightly different with every request. You will also see 53 "Message received" messages on the standard output. This proves that the message queue and database work are enlisted in the same global transaction.

JTA Transaction Manager Implications

This rather complex example demonstrated how to use global transactions with JTA. Even though Spring greatly simplifies global transaction management, the example is still quite complex, especially in terms of deployment and configuration. The performance of the XA queues and data sources is lower than the performance of their local versions, but the added benefit of a truly robust application makes this the only viable option in enterprise application development.

This is all good news; we now have a very robust and scalable application. The downside is that this application is very difficult to test. To do so, you need a J2EE application server to run the application, which means you can no longer write any unit tests that run outside of the container.

[1] This is application server specific. Thus, spring-ds.xml is going to work only in JBoss.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net