Example of Distributed Transactions

Today's example is to implement a distributed transaction that accesses two resource managers: a JDBC resource manager and a JMS provider. The example runs in both the WebLogic and JBoss server environments. The PointBase database is used for the WebLogic Server, and the HyperSonic database for JBoss server, whereas each server provides JMS service separately.

The example consists of a stateless session bean, UserManager, which uses JTA's distributed transaction to perform the following tasks as one unit of work:

  • Updates the database with student information

  • Sends a JMS message to the registration office

The registration office is modeled as a message-driven bean: RegistrarMDB.

Figure 16.4 depicts the components used in the example. Each component is described later in a separate listing.

Figure 16.4. Example of the UserManager EJB.


Listing 16.1 is for the remote interface UserManager, which lists all the business methods used in our example.

Listing 16.1. The Remote Interface UserManager.java
package day16;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
public interface UserManager extends EJBObject {
  public void connectUserManager()
         throws RemoteException;
  public void addCourse(String studentID, String courseID)
         throws RemoteException;
  public void notifyRegistrar(String studentID, String courseID)
         throws RemoteException;
  public void disconnectUserManager()
         throws RemoteException;
}

The home interface UserManagerHome in Listing 16.2 lists all the lifecycle methods used to manage the bean.

Listing 16.2. The Home Interface UserManagerHome.java
package day16;
import java.rmi.RemoteException;
import javax.ejb.*;
public interface UserManagerHome extends EJBHome {
   UserManager create() throws RemoteException, CreateException;
}

Listing 16.3 is the bean class UserManagerBean, which implements all the business methods listed in the remote interface and the callback methods to manage the bean's lifecycle by the container.

Listing 16.3. The SessionBean Class UserManagerBean.java
package day16;

import javax.ejb.*;
import javax.transaction.*;
import javax.sql.*;
import java.sql.*;
import javax.jms.*;
import javax.naming.*;

public class UserManagerBean implements SessionBean {
   private SessionContext ctx = null;
   private UserTransaction ut = null;
   private java.sql.Connection conn = null;
   private QueueSession qSession= null;
   private QueueConnection qConn = null;
   private Queue que = null;

   // Prepares the UserManager for use by starting a
   // new distributed transaction.
   public void connectUserManager(){
      try {
         // Establish a UserTransaction context from EJBContext
         ut = ctx.getUserTransaction();
         // Begin the distributed transaction
         System.out.println("Begin JTA distributed transaction. . .");
         ut.begin();
         // Setup database connection
         InitialContext jndiCtx = new InitialContext();
         // Create a handle to the DataSource
         System.out.println("  Connecting to JDBC data store...");
         DataSource ds =
            (DataSource)jndiCtx.lookup("java:comp/env/jdbc/styejbDB");
// Obtain a connection from the pool
         conn = ds.getConnection();

       } catch(Exception e) {
         try {
            ut = ctx.getUserTransaction();
            ut.rollback();
         } catch(Exception e2) {
            System.err.println(e2);
         }
      }
   }

   // Add a new course into the student account.
   // This can be called several times within the same transaction.
   public void addCourse(int studentID, String courseID){

      try {
         Statement stmt = conn.createStatement();
         try {
            System.out.println("  Trying to drop table StudentCourse...");
            stmt.execute("drop table StudentCourse");
            System.out.println("  Table StudentCourse dropped successfully...");
         } catch (SQLException e) {
            System.out.println("  Table StudentCourse did not exist.");
         }
         stmt.execute("create table StudentCourse " +
            " (id int, courseID varchar(6), is_registered varchar(1))");
         System.out.println("  Created new StudentCourse table.");
         stmt.execute("insert into StudentCourse values(" +
                      studentID + ",'" + courseID +  "','F')");
         System.out.println("  A new record is inserted " +
                  "into table StudentCourse. . .");
      } catch(Exception e) {
         try {
            ut = ctx.getUserTransaction();
            ut.rollback();
         } catch(Exception e2) {
            System.err.println(e2);
         }
      }
   }
   // Notify registration office by sending a copy of the enrolled course.
   // This can be called several times within the same transaction.
   public void notifyRegistrar(int studentID, String courseID){
      System.out.println("  Notify registration...");
      try {
         // Setup queue connection
         System.out.println("  Connecting to JMS destination...");
         InitialContext jndiCtx = new InitialContext();
         QueueConnectionFactory qcf = (QueueConnectionFactory)
                     jndiCtx.lookup ("ConnectionFactory");
         qConn = qcf.createQueueConnection();
         Queue que = (Queue)jndiCtx.lookup("java:comp/env/jms/RegistrarQ");
         qSession = qConn.createQueueSession(true,
                           QueueSession.AUTO_ACKNOWLEDGE);
         qConn.start();
         Thread.sleep(1000);
         QueueSender sender = qSession.createSender(que);
         TextMessage message = qSession.createTextMessage();
         message.setText("Student id=" + studentID +
                         " is enrolled in course=" + courseID);
         sender.send(message);
         System.out.println("  Message sent: " + message.getText());
      } catch(Exception e) {
         try {
            ut = ctx.getUserTransaction();
            ut.rollback();
         } catch(Exception e2) {
            System.err.println(e2);
         }
         System.err.println(e);
      }
   }
}
   // Close and commit the transaction
   public void disconnectUserManager(){
      try {
         // Get the UserTransaction instance
         ut = ctx.getUserTransaction();
         System.out.println("Committing the transaction. . .");
         // Commit the distributed transaction
         qConn.stop();
         conn.close();
         ut.commit();
      } catch(Exception e) {
         try {
            ut = ctx.getUserTransaction();
            ut.rollback();
         } catch(Exception e2){
           System.err.println(e2);
         }
         System.err.println(e);
      }
   }
   // The following methods to implement the SessionBean
   public void setSessionContext(SessionContext ctx) { this.ctx = ctx;}
   public void ejbCreate() {};
   public void ejbActivate() {};
   public void ejbPassivate() {};
   public void ejbRemove() {};
}

Listing 16.4 is the client to test the developed beans.

Listing 16.4. The Client Code to Access UserManager Client.java
package day16;

import javax.naming.*;
import javax.rmi.PortableRemoteObject;

public class Client {
   public static void main(String argv[]) {
   // A simple client deligates the UserManager session bean
   // to perform a JTA transaction. The JTA transaction will
   // be started in one method, then the context will be
   // propagated to other methods of the bean. Multiple resources:
   // a JDBC and a JMS resource manager are invloved in the
   // same distributed transaction.
         System.out.print("Demonstration the use of JTA 
");
   try {
         InitialContext ctx = new InitialContext();
         Object obj = ctx.lookup("day16/UserManagerHome");
         UserManagerHome userHome = (UserManagerHome)
              PortableRemoteObject.narrow(obj, UserManagerHome.class);
         UserManager user = userHome.create();
         // Delegate a UserManager to start a JTA transaction
         System.out.println("
Start global transaction...");
         user.connectUserManager();
         // Connect to a JDBC DataSource and add the enrolled course
         System.out.println("Adding courses to database...");
         user.addCourse(1, "CS310");
         // Connect to a JMS destination and send a message
         System.out.println("Notifying registrar queue...");
         user.notifyRegistrar(1, "CS310");
         // Commit the JTA transaction and release resources
         Thread.sleep(1000);
         System.out.println("Committing transaction...");
         user.disconnectUserManager();
   } catch(Exception e) {
         System.err.println(e.toString());
   }
 }
}

Listing 16.5 is the JMS receiver, a simple message-driven bean to receive the message and log it on the screen. This can be part of another distributed transaction, but we left this as an exercise for you. See the “Exercises” section at the end of the day.

Listing 16.5. The JMS Receiver RegistrarMDB.java
package day16;

import javax.ejb.MessageDrivenBean;
import javax.ejb.MessageDrivenContext;
import javax.ejb.EJBException;

import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.TextMessage;
import javax.naming.InitialContext;
import javax.naming.NamingException;
public class RegistrarMDB implements MessageDrivenBean, MessageListener{
  private MessageDrivenContext mctx =null;
  public void setMessageDrivenContext(MessageDrivenContext ctx) {mctx = ctx;}
  public void ejbCreate() {
         System.out.println("Instance of RegistrarMDB is created...");
  }
  public void ejbRemove() {}

  public void onMessage(Message message) {
    System.out.println("RegistrarMDB.onMessage: started..");
    try {
       TextMessage msg = (TextMessage)message;
       System.out.println("RegistrarMDB: Registrar received message: " +
                          msg.getText());
    } catch(JMSException e) {
       e.printStackTrace();
    }
  }
}

The deployment descriptor in Listing 16.6 combines the deployment information about both the UserManager and the RegistrarMDB.

Listing 16.6. Standard Deployment Descriptor ejb-jar.xml
<?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>
    <session>
      <ejb-name>UserManager</ejb-name>
      <home>day16.UserManagerHome</home>
      <remote>day16.UserManager</remote>
      <ejb-class>day16.UserManagerBean</ejb-class>
      <session-type>Stateful</session-type>
      <transaction-type>Bean</transaction-type>
       <resource-env-ref>
						<resource-env-ref-name>jdbc/styejbDB</resource-env-ref-name>
						<resource-env-ref-type>javax.sql.DataSource</resource-env-ref-type>
						</resource-env-ref>
       <resource-env-ref>
             <resource-env-ref-name>jms/RegistrarQ</resource-env-ref-name>
    <resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
       </resource-env-ref>
    </session>
    <message-driven>
      <ejb-name>RegistrarMDB</ejb-name>
      <ejb-class>day16.RegistrarMDB</ejb-class>
      <transaction-type>Container</transaction-type>
      <message-driven-destination>
        <destination-type>javax.jms.Queue</destination-type>
      </message-driven-destination>
      <resource-ref>
        <res-ref-name>jms/QCF</res-ref-name>
        <res-type>javax.jms.QueueConnectionFactory</res-type>
         <res-auth>Container</res-auth>
      </resource-ref>
     </message-driven>
  </enterprise-beans>
</ejb-jar>

The deployment descriptor for WebLogic Server and JBoss are listed in Listings 16.7 and 16.8.

Listing 16.7. WebLogic Deployment Descriptor weblogic-ejb-jar.xml
<?xml version="1.0"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC
'-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN'
'http://www.bea.com/servers/wls600/dtd/weblogic-ejb-jar.dtd'>

<weblogic-ejb-jar>
  <weblogic-enterprise-bean>
    <ejb-name>UserManager</ejb-name>
    <reference-descriptor>
						<resource-env-description>
						<res-env-ref-name>jdbc/styejbDB</res-env-ref-name>
						<jndi-name>jdbc.styejbDB</jndi-name>
						</resource-env-description>
						<resource-env-description>
         <res-env-ref-name>jms/RegistrarQ</res-env-ref-name>
         <jndi-name>RegistrarQ</jndi-name>
      </resource-env-description>
    </reference-descriptor>
    <jndi-name>day16/UserManagerHome</jndi-name>
  </weblogic-enterprise-bean>
  <weblogic-enterprise-bean>
    <ejb-name>RegistrarMDB</ejb-name>
     <message-driven-descriptor>
       <pool>
        <max-beans-in-free-pool>5</max-beans-in-free-pool>
        <initial-beans-in-free-pool>1</initial-beans-in-free-pool>
       </pool>
       <destination-jndi-name>RegistrarQ</destination-jndi-name>
     </message-driven-descriptor>
    <reference-descriptor>
      <resource-description>
        <res-ref-name>jms/QCF</res-ref-name>
   <jndi-name>ConnectionFactory</jndi-name>
      </resource-description>
    </reference-descriptor>
    <jndi-name>RegistrarQ</jndi-name>
   </weblogic-enterprise-bean>
 </weblogic-ejb-jar>

Listing 16.8. JBoss Deployment Descriptor jboss.xml
<?xml version="1.0" encoding="UTF-8"?>

<jboss>
  <enterprise-beans>
    <session>
      <ejb-name>UserManager</ejb-name>
      <jndi-name>day16/UserManagerHome</jndi-name>
       <resource-env-ref>
						<resource-env-ref-name>jdbc/styejbDB</resource-env-ref-name>
						<jndi-name>java:/styejbDB</jndi-name>
						</resource-env-ref>
       <resource-env-ref>
         <resource-env-ref-name>jms/RegistrarQ</resource-env-ref-name>
          <jndi-name>queue/RegistrarQ</jndi-name>
       </resource-env-ref>
    </session>
    <message-driven>
     <ejb-name>RegistrarMDB</ejb-name>
     <destination-jndi-name>queue/RegisterQ</destination-jndi-name>
     <resource-ref>
       <res-ref-name>jms/QCF</res-ref-name>
       <jndi-name>ConnectionFactory</jndi-name>
     </resource-ref>
    </message-driven>
 </enterprise-beans>
</jboss>

Part of making your application portable is to use a <resource-env-ref> element in your standard deployment descriptor (ejb-jar.xml) to define a logical name used by the application. Then you need to map this logical name to a <reference-descriptor> in your server-specific deployment descriptor. An example is the usage of the logical name "jdbc/styejbDB" for the JDBC DataSource, which is shown above in bold in Listing 16.6, 16.7, and 16.8.

Build and Run the Example

To build the example, a build script is provided for WebLogic Server and the JBoss server.

1.
Configure both the connection pool styejbPool and the JDBC DataSource styejbDB as described in Day 9.

2.
Configure both the ConnectionFactory and the Destination Queue as described in Day 14.

3.
Build the example for the appropriate application server. From the directory Day16, run the build script. This creates a subdirectory named build that contains all the compiled code:

c:>cd c:styejb
c:styejb>setEnvWebLogic.bat
c:styejb>cd day16
c:styejbday16>buildWebLogic.bat
								

4.
To run the example, use the appropriate script for each server. Set up the environment for the client in a new command window, and then use the run script in the Day16 directory:

c:styejb>setEnvWebLogic.bat
c:styejb>cd day16
c:styejbday16> runClientWebLogic.bat
								

In order to run the example on the JBoss application server, use the appropriate scripts in the same directory.

The following is the expected output of the example on the server side:

Begin JTA distributed transaction. . .
  Connecting to JDBC data store...
  Trying to drop table StudentCourse...
      table StudentCourse dropped successfully...
  Created new StudentCourse table.
  A new record is inserted into table StudentCourse. . .
  Notify registration...
  Connecting to JMS destination...
  Message sent: Student id=1 is enrolled in course=CS310
Committing the transaction. . .

On the client side, this is the expected output:

Start global transaction...
Adding courses to database.
Notifying registrar queue..
Committing transaction...

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.171.51