The Java EE 5 Tutorial

Using JMS API Local Transactions

You can group a series of operations into an atomic unit of work called a transaction. If any one of the operations fails, the transaction can be rolled back, and the operations can be attempted again from the beginning. If all the operations succeed, the transaction can be committed.

In a JMS client, you can use local transactions to group message sends and receives. The JMS API Session interface provides commit and rollback methods that you can use in a JMS client. A transaction commit means that all produced messages are sent and all consumed messages are acknowledged. A transaction rollback means that all produced messages are destroyed and all consumed messages are recovered and redelivered unless they have expired (see Allowing Messages to Expire).

A transacted session is always involved in a transaction. As soon as the commit or the rollback method is called, one transaction ends and another transaction begins. Closing a transacted session rolls back its transaction in progress, including any pending sends and receives.

In an Enterprise JavaBeans component, you cannot use the Session.commit and Session.rollback methods. Instead, you use distributed transactions, which are described in Using the JMS API in a Java EE Application.

You can combine several sends and receives in a single JMS API local transaction. If you do so, you need to be careful about the order of the operations. You will have no problems if the transaction consists of all sends or all receives or if the receives come before the sends. But if you try to use a request/reply mechanism, whereby you send a message and then try to receive a reply to the sent message in the same transaction, the program will hang, because the send cannot take place until the transaction is committed. The following code fragment illustrates the problem:

// Don’t do this!
outMsg.setJMSReplyTo(replyQueue);
producer.send(outQueue, outMsg);
consumer = session.createConsumer(replyQueue);
inMsg = consumer.receive();
session.commit();

Because a message sent during a transaction is not actually sent until the transaction is committed, the transaction cannot contain any receives that depend on that message’s having been sent.

In addition, the production and the consumption of a message cannot both be part of the same transaction. The reason is that the transactions take place between the clients and the JMS provider, which intervenes between the production and the consumption of the message. Figure 31–9 illustrates this interaction.

Figure 31–9 Using JMS API Local Transactions

Diagram of local transactions, showing separate transactions
for sending and consuming a message

The sending of one or more messages to one or more destinations by client 1 can form a single transaction, because it forms a single set of interactions with the JMS provider using a single session. Similarly, the receiving of one or more messages from one or more destinations by client 2 also forms a single transaction using a single session. But because the two clients have no direct interaction and are using two different sessions, no transactions can take place between them.

Another way of putting this is that the act of producing and/or consuming messages in a session can be transactional, but the act of producing and consuming a specific message across different sessions cannot be transactional.

This is the fundamental difference between messaging and synchronized processing. Instead of tightly coupling the sending and receiving of data, message producers and consumers use an alternative approach to reliability, one that is built on a JMS provider’s ability to supply a once-and-only-once message delivery guarantee.

When you create a session, you specify whether it is transacted. The first argument to the createSession method is a boolean value. A value of true means that the session is transacted; a value of false means that it is not transacted. The second argument to this method is the acknowledgment mode, which is relevant only to nontransacted sessions (see Controlling Message Acknowledgment). If the session is transacted, the second argument is ignored, so it is a good idea to specify 0 to make the meaning of your code clear. For example:

session = connection.createSession(true, 0);

The commit and the rollback methods for local transactions are associated with the session. You can combine queue and topic operations in a single transaction if you use the same session to perform the operations. For example, you can use the same session to receive a message from a queue and send a message to a topic in the same transaction.

You can pass a client program’s session to a message listener’s constructor function and use it to create a message producer. In this way, you can use the same session for receives and sends in asynchronous message consumers.

The next section provides an example of the use of JMS API local transactions.

A Local Transaction Example

The TransactedExample.java program demonstrates the use of transactions in a JMS client application. The program is in the following directory:

tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/src/java/

    This example shows how to use a queue and a topic in a single transaction as well as how to pass a session to a message listener’s constructor function. The program represents a highly simplified e-commerce application in which the following things happen.

  1. A retailer sends a MapMessage to the vendor order queue, ordering a quantity of computers, and waits for the vendor’s reply:

    producer = session.createProducer(vendorOrderQueue);
    outMessage = session.createMapMessage();
    outMessage.setString("Item", "Computer(s)");
    outMessage.setInt("Quantity", quantity);
    outMessage.setJMSReplyTo(retailerConfirmQueue);
    producer.send(outMessage);
    System.out.println("Retailer: ordered " + quantity + " computer(s)");
    orderConfirmReceiver = session.createConsumer(retailerConfirmQueue);
    connection.start();
  2. The vendor receives the retailer’s order message and sends an order message to the supplier order topic in one transaction. This JMS transaction uses a single session, so you can combine a receive from a queue with a send to a topic. Here is the code that uses the same session to create a consumer for a queue and a producer for a topic:

    vendorOrderReceiver = session.createConsumer(vendorOrderQueue);
    supplierOrderProducer = session.createProducer(supplierOrderTopic);

    The following code receives the incoming message, sends an outgoing message, and commits the session. The message processing has been removed to keep the sequence simple:

    inMessage = vendorOrderReceiver.receive();
    // Process the incoming message and format the outgoing 
    // message
    ...
    supplierOrderProducer.send(orderMessage);
    ...
    session.commit();
  3. Each supplier receives the order from the order topic, checks its inventory, and then sends the items ordered to the queue named in the order message’s JMSReplyTo field. If it does not have enough in stock, the supplier sends what it has. The synchronous receive from the topic and the send to the queue take place in one JMS transaction.

    receiver = session.createConsumer(orderTopic);
    ...
    inMessage = receiver.receive();
    if (inMessage instanceof MapMessage) {
        orderMessage = (MapMessage) inMessage;
    }
    // Process message
    MessageProducer producer = 
        session.createProducer((Queue) orderMessage.getJMSReplyTo());
    outMessage = session.createMapMessage();
    // Add content to message
    producer.send(outMessage);
    // Display message contentssession.commit();
  4. The vendor receives the replies from the suppliers from its confirmation queue and updates the state of the order. Messages are processed by an asynchronous message listener; this step shows the use of JMS transactions with a message listener.

    MapMessage component = (MapMessage) message;
    ...
    orderNumber = component.getInt("VendorOrderNumber");
    Order order = Order.getOrder(orderNumber).processSubOrder(component);
    session.commit();
  5. When all outstanding replies are processed for a given order, the vendor message listener sends a message notifying the retailer whether it can fulfill the order.

    Queue replyQueue = (Queue) order.order.getJMSReplyTo();
    MessageProducer producer = session.createProducer(replyQueue);
    MapMessage retailerConfirmMessage = session.createMapMessage();
    // Format the message
    producer.send(retailerConfirmMessage);
    session.commit();
  6. The retailer receives the message from the vendor:

    inMessage = (MapMessage) orderConfirmReceiver.receive();

Figure 31–10 illustrates these steps.

Figure 31–10 Transactions: JMS Client Example

Diagram of steps in transaction example

The program contains five classes: Retailer, Vendor, GenericSupplier, VendorMessageListener, and Order. The program also contains a main method and a method that runs the threads of the Retailer, Vendor, and two supplier classes.

All the messages use the MapMessage message type. Synchronous receives are used for all message reception except for the case of the vendor processing the replies of the suppliers. These replies are processed asynchronously and demonstrate how to use transactions within a message listener.

At random intervals, the Vendor class throws an exception to simulate a database problem and cause a rollback.

All classes except Retailer use transacted sessions.

The program uses three queues named jms/AQueue, jms/BQueue, and jms/CQueue, and one topic named jms/OTopic.

    Before you run the program, do the following:

  1. In a terminal window, go to the following directory:

    tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/
    
  2. Create the necessary resources using the following command:


    ant create-resources
    

    This command creates three destination resources with the names jms/AQueue, jms/BQueue, and jms/CQueue, all of type javax.jms.Queue, and one destination resource with the name jms/OTopic, of type javax.jms.Topic.

  3. To compile and package the program using NetBeans IDE, follow these steps:

    1. In NetBeans IDE, choose Open Project from the File menu.

    2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/advanced/.

    3. Select the transactedexample folder.

    4. Select the Open as Main Project check box.

    5. Click Open Project.

    6. Right-click the project and choose Build.

    To compile and package the program using Ant, follow these steps:

    1. Go to the following directory:

      tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/
      
    2. Type the following command:


      ant
      

    To run the program using NetBeans IDE, follow these steps:

  1. Right-click the transactedexample project and choose Properties.

  2. Select Run from the Categories tree.

  3. In the Arguments field, type a number that specifies the number of computers to order:

    3

  4. Click OK.

  5. Right-click the project and choose Run.

    To run the program from the command line, follow these steps:

  1. Go to the dist directory:


    cd dist
    
  2. Use a command like the following to run the program. The argument specifies the number of computers to order:


    appclient -client transactedexample.jar 3
    

The output looks something like this:


Quantity to be ordered is 3
Retailer: ordered 3 computer(s)
Vendor: Retailer ordered 3 Computer(s)
Vendor: ordered 3 monitor(s) and hard drive(s)
Monitor Supplier: Vendor ordered 3 Monitor(s)
Monitor Supplier: sent 3 Monitor(s)
  Monitor Supplier: committed transaction
  Vendor: committed transaction 1
Hard Drive Supplier: Vendor ordered 3 Hard Drive(s)
Hard Drive Supplier: sent 1 Hard Drive(s)
Vendor: Completed processing for order 1
  Hard Drive Supplier: committed transaction
Vendor: unable to send 3 computer(s)
  Vendor: committed transaction 2
Retailer: Order not filled
Retailer: placing another order
Retailer: ordered 6 computer(s)
Vendor: JMSException occurred: javax.jms.JMSException: 
Simulated database concurrent access exception
javax.jms.JMSException: Simulated database concurrent access exception
        at TransactedExample$Vendor.run(Unknown Source)
  Vendor: rolled back transaction 1
Vendor: Retailer ordered 6 Computer(s)
Vendor: ordered 6 monitor(s) and hard drive(s)
Monitor Supplier: Vendor ordered 6 Monitor(s)
Hard Drive Supplier: Vendor ordered 6 Hard Drive(s)
Monitor Supplier: sent 6 Monitor(s)
  Monitor Supplier: committed transaction
Hard Drive Supplier: sent 6 Hard Drive(s)
  Hard Drive Supplier: committed transaction
  Vendor: committed transaction 1
Vendor: Completed processing for order 2
Vendor: sent 6 computer(s)
Retailer: Order filled
  Vendor: committed transaction 2

After you run the program, you can delete the physical destinations and the destination resources. Go to the directory tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/ and type the following command:


ant delete-resources

Use the following command to remove the class and JAR files:


ant clean