context.createProducer()
.setDeliveryMode(DeliveryMode.NON_PERSISTENT).send(dest, msg);
Java Platform, Enterprise Edition (Java EE) 8 The Java EE Tutorial |
Previous | Next | Contents |
This section explains how to use features of the JMS API to achieve the level of reliability and performance your application requires. Many people use JMS in their applications because they cannot tolerate dropped or duplicate messages and because they require that every message be received once and only once. The JMS API provides this functionality.
The most reliable way to produce a message is to send a PERSISTENT
message, and to do so within a transaction.
JMS messages are PERSISTENT
by default; PERSISTENT
messages will not
be lost in the event of JMS provider failure. For details, see
Specifying Message Persistence.
Transactions allow multiple messages to be sent or received in an atomic operation. In the Java EE platform they also allow message sends and receives to be combined with database reads and writes in an atomic transaction. A transaction is a unit of work into which you can group a series of operations, such as message sends and receives, so that the operations either all succeed or all fail. For details, see Using JMS Local Transactions.
The most reliable way to consume a message is to do so within a transaction, either from a queue or from a durable subscription to a topic. For details, see Creating Durable Subscriptions, Creating Temporary Destinations, and Using JMS Local Transactions.
Some features primarily allow an application to improve performance. For example, you can set messages to expire after a certain length of time (see Allowing Messages to Expire), so that consumers do not receive unnecessary outdated information. You can send messages asynchronously; see Sending Messages Asynchronously.
You can also specify various levels of control over message acknowledgment; see Controlling Message Acknowledgment.
Other features can provide useful capabilities unrelated to reliability. For example, you can create temporary destinations that last only for the duration of the connection in which they are created. See Creating Temporary Destinations for details.
The following sections describe these features as they apply to application clients or Java SE clients. Some of the features work differently in the Java EE web or EJB container; in these cases, the differences are noted here and are explained in detail in Using the JMS API in Java EE Applications.
Until a JMS message has been acknowledged, it is not considered to be successfully consumed. The successful consumption of a message ordinarily takes place in three stages.
The client receives the message.
The client processes the message.
The message is acknowledged. Acknowledgment is initiated either by the JMS provider or by the client, depending on the session acknowledgment mode.
In locally transacted sessions (see Using JMS Local Transactions), a message is acknowledged when the session is committed. If a transaction is rolled back, all consumed messages are redelivered.
In a JTA transaction (in the Java EE web or EJB container) a message is acknowledged when the transaction is committed.
In nontransacted sessions, when and how a message is acknowledged depend
on a value that may be specified as an argument of the createContext
method. The possible argument values are as follows.
JMSContext.AUTO_ACKNOWLEDGE
: This setting is the default for
application clients and Java SE clients. The JMSContext
automatically
acknowledges a client’s receipt of a message either when the client has
successfully returned from a call to receive
or when the
MessageListener
it has called to process the message returns
successfully.
A synchronous receive in a JMSContext
that is configured to use
auto-acknowledgment is the one exception to the rule that message
consumption is a three-stage process as described earlier. In this case,
the receipt and acknowledgment take place in one step, followed by the
processing of the message.
JMSContext.CLIENT_ACKNOWLEDGE
: A client acknowledges a message by
calling the message’s acknowledge
method. In this mode, acknowledgment
takes place on the session level: Acknowledging a consumed message
automatically acknowledges the receipt of all messages that have been
consumed by its session. For example, if a message consumer consumes ten
messages and then acknowledges the fifth message delivered, all ten
messages are acknowledged.
Note: In the Java EE platform, the |
JMSContext.DUPS_OK_ACKNOWLEDGE
: This option instructs the
JMSContext
to lazily acknowledge the delivery of messages. This is
likely to result in the delivery of some duplicate messages if the JMS
provider fails, so it should be used only by consumers that can tolerate
duplicate messages. (If the JMS provider redelivers a message, it must
set the value of the JMSRedelivered
message header to true
.) This
option can reduce session overhead by minimizing the work the session
does to prevent duplicates.
If messages have been received from a queue but not acknowledged when a
JMSContext
is closed, the JMS provider retains them and redelivers
them when a consumer next accesses the queue. The provider also retains
unacknowledged messages if an application closes a JMSContext
that has
been consuming messages from a durable subscription. (See
Creating Durable Subscriptions.)
Unacknowledged messages that have been received from a nondurable
subscription will be dropped when the JMSContext
is closed.
If you use a queue or a durable subscription, you can use the
JMSContext.recover
method to stop a nontransacted JMSContext
and
restart it with its first unacknowledged message. In effect, the
JMSContext
's series of delivered messages is reset to the point after
its last acknowledged message. The messages it now delivers may be
different from those that were originally delivered, if messages have
expired or if higher-priority messages have arrived. For a consumer on a
nondurable subscription, the provider may drop unacknowledged messages
when the JMSContext.recover
method is called.
The sample program in Acknowledging Messages demonstrates two ways to ensure that a message will not be acknowledged until processing of the message is complete.
You can set a number of options when you send a message. These options enable you to perform the tasks described in the following topics:
Specifying Message Persistence – Specify that messages are persistent, meaning they must not be lost in the event of a provider failure.
Setting Message Priority Levels – Set priority levels for messages, which can affect the order in which the messages are delivered.
Allowing Messages to Expire – Specify an expiration time for messages so they will not be delivered if they are obsolete.
Specifying a Delivery Delay– Specify a delivery delay for messages so that they will not be delivered until a specified amount of time has expired.
Using JMSProducer Method Chaining – Method chaining
allows you to specify more than one of these options when you create a
producer and call the send
method.
The JMS API supports two delivery modes specifying whether messages are
lost if the JMS provider fails. These delivery modes are fields of the
DeliveryMode
interface.
The default delivery mode, PERSISTENT
, instructs the JMS provider to
take extra care to ensure that a message is not lost in transit in case
of a JMS provider failure. A message sent with this delivery mode is
logged to stable storage when it is sent.
The NON_PERSISTENT
delivery mode does not require the JMS provider
to store the message or otherwise guarantee that it is not lost if the
provider fails.
To specify the delivery mode, use the setDeliveryMode
method of the
JMSProducer
interface to set the delivery mode for all messages sent
by that producer.
You can use method chaining to set the delivery mode when you create a
producer and send a message. The following call creates a producer with
a NON_PERSISTENT
delivery mode and uses it to send a message:
context.createProducer()
.setDeliveryMode(DeliveryMode.NON_PERSISTENT).send(dest, msg);
If you do not specify a delivery mode, the default is PERSISTENT
.
Using the NON_PERSISTENT
delivery mode may improve performance and
reduce storage overhead, but you should use it only if your application
can afford to miss messages.
You can use message priority levels to instruct the JMS provider to
deliver urgent messages first. Use the setPriority
method of the
JMSProducer
interface to set the priority level for all messages sent
by that producer.
You can use method chaining to set the priority level when you create a producer and send a message. For example, the following call sets a priority level of 7 for a producer and then sends a message:
context.createProducer().setPriority(7).send(dest, msg);
The ten levels of priority range from 0 (lowest) to 9 (highest). If you do not specify a priority level, the default level is 4. A JMS provider tries to deliver higher-priority messages before lower-priority ones, but does not have to deliver messages in exact order of priority.
By default, a message never expires. If a message will become obsolete
after a certain period, however, you may want to set an expiration time.
Use the setTimeToLive
method of the JMSProducer
interface to set a
default expiration time for all messages sent by that producer.
For example, a message that contains rapidly changing data such as a stock price will become obsolete after a few minutes, so you might configure messages to expire after that time.
You can use method chaining to set the time to live when you create a producer and send a message. For example, the following call sets a time to live of five minutes for a producer and then sends a message:
context.createProducer().setTimeToLive(300000).send(dest, msg);
If the specified timeToLive
value is 0
, the message never expires.
When the message is sent, the specified timeToLive
is added to the
current time to give the expiration time. Any message not delivered
before the specified expiration time is destroyed. The destruction of
obsolete messages conserves storage and computing resources.
You can specify a length of time that must elapse after a message is
sent before the JMS provider delivers the message. Use the
setDeliveryDelay
method of the JMSProducer
interface to set a
delivery delay for all messages sent by that producer.
You can use method chaining to set the delivery delay when you create a producer and send a message. For example, the following call sets a delivery delay of 3 seconds for a producer and then sends a message:
context.createProducer().setDeliveryDelay(3000).send(dest, msg);
The setter methods on the JMSProducer
interface return JMSProducer
objects, so you can use method chaining to create a producer, set
multiple properties, and send a message. For example, the following
chained method calls create a producer, set a user-defined property, set
the expiration, delivery mode, and priority for the message, and then
send a message to a queue:
context.createProducer()
.setProperty("MyProperty", "MyValue")
.setTimeToLive(10000)
.setDeliveryMode(NON_PERSISTENT)
.setPriority(2)
.send(queue, body);
You can also call the JMSProducer
methods to set properties on a
message and then send the message in a separate send
method call. You
can also set message properties directly on a message.
Normally, you create JMS destinations (queues and topics) administratively rather than programmatically. Your JMS provider includes a tool to create and remove destinations, and it is common for destinations to be long-lasting.
The JMS API also enables you to create destinations (TemporaryQueue
and TemporaryTopic
objects) that last only for the duration of the
connection in which they are created. You create these destinations
dynamically using the JMSContext.createTemporaryQueue
and the
JMSContext.createTemporaryTopic
methods, as in the following example:
TemporaryTopic replyTopic = context.createTemporaryTopic();
The only message consumers that can consume from a temporary destination are those created by the same connection that created the destination. Any message producer can send to the temporary destination. If you close the connection to which a temporary destination belongs, the destination is closed and its contents are lost.
You can use temporary destinations to implement a simple request/reply
mechanism. If you create a temporary destination and specify it as the
value of the JMSReplyTo
message header field when you send a message,
then the consumer of the message can use the value of the JMSReplyTo
field as the destination to which it sends a reply. The consumer can
also reference the original request by setting the JMSCorrelationID
header field of the reply message to the value of the JMSMessageID
header field of the request. For example, an onMessage
method can
create a JMSContext
so that it can send a reply to the message it
receives. It can use code such as the following:
replyMsg = context.createTextMessage("Consumer processed message: "
+ msg.getText());
replyMsg.setJMSCorrelationID(msg.getJMSMessageID());
context.createProducer().send((Topic) msg.getJMSReplyTo(), replyMsg);
For an example, see Using an Entity to Join Messages from Two MDBs.
A transaction groups a series of operations into an atomic unit of work. 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 an application client or a Java SE client, you can use local
transactions to group message sends and receives. You use the
JMSContext.commit
method to commit a transaction. You can send
multiple messages in a transaction, and the messages will not be added
to the queue or topic until the transaction is committed. If you receive
multiple messages in a transaction, they will not be acknowledged until
the transaction is committed.
You can use the JMSContext.rollback
method to roll back a transaction.
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. To create a
transacted session, call the createContext
method as follows:
JMSContext context =
connectionFactory.createContext(JMSContext.SESSION_TRANSACTED);
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 application running in the Java EE web or EJB container, you cannot use local transactions. Instead, you use JTA transactions, described in Using the JMS API in Java EE Applications.
You can combine several sends and receives in a single JMS local
transaction, so long as they are all performed using the same
JMSContext
.
Do not use a single transaction if you use a request/reply mechanism, in which you send a message and then receive a reply to that message. If you try to use a single 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);
context.createProducer().send(outQueue, outMsg);
consumer = context.createConsumer(replyQueue);
inMsg = consumer.receive();
context.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.
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 48-8 illustrates this interaction.
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 JMSContext
.
Similarly, the receiving of one or more messages from one or more
destinations by Client 2 also forms a single transaction using a single
JMSContext
. But because the two clients have no direct interaction and
are using two different JMSContext
objects, no transactions can take
place between them.
Another way of putting this is that a transaction is a contract between a client and a JMS provider that defines whether a message is sent to a destination or whether a message is received from the destination. It is not a contract between the sending client and the receiving client.
This is the fundamental difference between messaging and synchronized processing. Instead of tightly coupling the sender and the receiver of a message, JMS couples the sender of a message with the destination, and it separately couples the destination with the receiver of the message. Therefore, while the sends and receives each have a tight coupling with the JMS provider, they do not have any coupling with each other.
When you create a JMSContext
, you can specify whether it is transacted
by using the JMSContext.SESSION_TRANSACTED
argument to the
createContext
method. For example:
try (JMSContext context = connectionFactory.createContext(
JMSContext.SESSION_TRANSACTED);) {
...
The commit
and the rollback
methods for local transactions are
associated with the session that underlies the JMSContext
. You can
combine operations on more than one queue or topic, or on a combination
of queues and topics, in a single transaction if you use the same
session to perform the operations. For example, you can use the same
JMSContext
to receive a message from a queue and send a message to a
topic in the same transaction.
The example in Using Local Transactions shows how to use JMS local transactions.
Normally, when you send a persistent message, the send
method blocks
until the JMS provider confirms that the message was sent successfully.
The asynchronous send mechanism allows your application to send a
message and continue work while waiting to learn whether the send
completed.
This feature is currently available only in application clients and Java SE clients.
Sending a message asynchronously involves supplying a callback object.
You specify a CompletionListener
with an onCompletion
method. For
example, the following code instantiates a CompletionListener
named
SendListener
. It then calls the setAsync
method to specify that
sends from this producer should be asynchronous and should use the
specified listener:
CompletionListener listener = new SendListener();
context.createProducer().setAsync(listener).send(dest, message);
The CompletionListener
class must implement two methods,
onCompletion
and onException
. The onCompletion
method is called if
the send succeeds, and the onException
method is called if it fails. A
simple implementation of these methods might look like this:
@Override
public void onCompletion(Message message) {
System.out.println("onCompletion method: Send has completed.");
}
@Override
public void onException(Message message, Exception e) {
System.out.println("onException method: send failed: " + e.toString());
System.out.println("Unsent message is: \n" + message);
}
Previous | Next | Contents |