Browse Source

Clarify acknowledge mode semantics and transaction recommendations

Issue: SPR-13278
pull/849/head
Juergen Hoeller 9 years ago
parent
commit
e5a2b34829
  1. 76
      spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java
  2. 3
      spring-jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java
  3. 41
      src/asciidoc/integration.adoc

76
spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java

@ -37,19 +37,19 @@ import org.springframework.util.ErrorHandler; @@ -37,19 +37,19 @@ import org.springframework.util.ErrorHandler;
import org.springframework.util.ReflectionUtils;
/**
* Abstract base class for message listener containers. Can either host
* a standard JMS {@link javax.jms.MessageListener} or a Spring-specific
* {@link SessionAwareMessageListener}.
* Abstract base class for Spring message listener container implementations.
* Can either host a standard JMS {@link javax.jms.MessageListener} or Spring's
* {@link SessionAwareMessageListener} for actual message processing.
*
* <p>Usually holds a single JMS {@link Connection} that all listeners are
* supposed to be registered on, which is the standard JMS way of managing
* listeners. Can alternatively also be used with a fresh Connection per
* listener, for J2EE-style XA-aware JMS messaging. The actual registration
* process is up to concrete subclasses.
* <p>Usually holds a single JMS {@link Connection} that all listeners are supposed
* to be registered on, which is the standard JMS way of managing listener sessions.
* Can alternatively also be used with a fresh Connection per listener, for Java EE
* style XA-aware JMS messaging. The actual registration process is up to concrete
* subclasses.
*
* <p><b>NOTE:</b> The default behavior of this message listener container
* is to <b>never</b> propagate an exception thrown by a message listener up to
* the JMS provider. Instead, it will log any such exception at the error level.
* <p><b>NOTE:</b> The default behavior of this message listener container is to
* <b>never</b> propagate an exception thrown by a message listener up to the JMS
* provider. Instead, it will log any such exception at the error level.
* This means that from the perspective of the attendant JMS provider no such
* listener will ever fail. However, if error handling is necessary, then
* any implementation of the {@link ErrorHandler} strategy may be provided to
@ -62,37 +62,48 @@ import org.springframework.util.ReflectionUtils; @@ -62,37 +62,48 @@ import org.springframework.util.ReflectionUtils;
* <li>"sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default):
* This mode is container-dependent: For {@link DefaultMessageListenerContainer},
* it means automatic message acknowledgment <i>before</i> listener execution, with
* no redelivery in case of an exception. For {@link SimpleMessageListenerContainer},
* no redelivery in case of an exception and no redelivery in case of other listener
* execution interruptions either. For {@link SimpleMessageListenerContainer},
* it means automatic message acknowledgment <i>after</i> listener execution, with
* redelivery in case of an exception thrown, as defined by the JMS specification.
* In order to consistently achieve the latter behavior with any container variant,
* consider setting "sessionTransacted" to "true" instead.
* no redelivery in case of a user exception thrown but potential redelivery in case
* of the JVM dying during listener execution. In order to consistently arrange for
* redelivery with any container variant, consider "CLIENT_ACKNOWLEDGE" mode or -
* preferably - setting "sessionTransacted" to "true" instead.
* <li>"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE":
* <i>Lazy</i> message acknowledgment during ({@link DefaultMessageListenerContainer})
* or shortly after ({@link SimpleMessageListenerContainer}) listener execution;
* no redelivery in case of a user exception thrown but potential redelivery in case
* of the JVM dying during listener execution. In order to consistently arrange for
* redelivery with any container variant, consider "CLIENT_ACKNOWLEDGE" mode or -
* preferably - setting "sessionTransacted" to "true" instead.
* <li>"sessionAcknowledgeMode" set to "CLIENT_ACKNOWLEDGE":
* Automatic message acknowledgment <i>after</i> successful listener execution;
* best-effort redelivery in case of exception thrown.
* <li>"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE":
* <i>Lazy</i> message acknowledgment during or after listener execution;
* <i>potential redelivery</i> in case of exception thrown.
* best-effort redelivery in case of a user exception thrown as well as in case
* of other listener execution interruptions (such as the JVM dying).
* <li>"sessionTransacted" set to "true":
* Transactional acknowledgment after successful listener execution;
* <i>guaranteed redelivery</i> in case of exception thrown.
* <i>guaranteed redelivery</i> in case of a user exception thrown as well as
* in case of other listener execution interruptions (such as the JVM dying).
* </ul>
* The exact behavior might vary according to the concrete listener container
* and JMS provider used.
*
* <p>There are two solutions to the duplicate processing problem:
* <p>There are two solutions to the duplicate message processing problem:
* <ul>
* <li>Either add <i>duplicate message detection</i> to your listener, in the
* form of a business entity existence check or a protocol table check. This
* usually just needs to be done in case of the JMSRedelivered flag being
* set on the incoming message (else just process straightforwardly).
* <li>Or wrap the <i>entire processing with an XA transaction</i>, covering the
* reception of the message as well as the execution of the message listener.
* This is only supported by {@link DefaultMessageListenerContainer}, through
* specifying a "transactionManager" (typically a
* set on the incoming message (otherwise just process straightforwardly).
* Note that with "sessionTransacted" set to "true", duplicate messages will
* only appear in case of the JVM dying at the most unfortunate point possible
* (i.e. after your business logic executed but before the JMS part got committed),
* so duplicate message detection is just there to cover a corner case.
* <li>Or wrap your <i>entire processing with an XA transaction</i>, covering the
* reception of the JMS message as well as the execution of the business logic in
* your message listener (including database operations etc). This is only
* supported by {@link DefaultMessageListenerContainer}, through specifying
* an external "transactionManager" (typically a
* {@link org.springframework.transaction.jta.JtaTransactionManager}, with
* a corresponding XA-aware JMS {@link javax.jms.ConnectionFactory} passed in as
* "connectionFactory").
* a corresponding XA-aware JMS {@link javax.jms.ConnectionFactory} passed in
* as "connectionFactory").
* </ul>
* Note that XA transaction coordination adds significant runtime overhead,
* so it might be feasible to avoid it unless absolutely necessary.
@ -108,7 +119,7 @@ import org.springframework.util.ReflectionUtils; @@ -108,7 +119,7 @@ import org.springframework.util.ReflectionUtils;
* <li>Alternatively, specify a
* {@link org.springframework.transaction.jta.JtaTransactionManager} as
* "transactionManager" for a fully XA-aware JMS provider - typically when
* running on a J2EE server, but also for other environments with a JTA
* running on a Java EE server, but also for other environments with a JTA
* transaction manager present. This will give full "exactly-once" guarantees
* without custom duplicate message checks, at the price of additional
* runtime processing overhead.
@ -921,7 +932,7 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen @@ -921,7 +932,7 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
if (errorHandler != null) {
errorHandler.handleError(ex);
}
else if (logger.isWarnEnabled()) {
else {
logger.warn("Execution of JMS message listener failed, and no ErrorHandler has been set.", ex);
}
}
@ -933,7 +944,6 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen @@ -933,7 +944,6 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
*/
@SuppressWarnings("serial")
private static class MessageRejectedWhileStoppingException extends RuntimeException {
}
}

3
spring-jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java

@ -47,7 +47,8 @@ import org.springframework.util.Assert; @@ -47,7 +47,8 @@ import org.springframework.util.Assert;
* on acknowledge modes and transaction options. Note that this container
* exposes standard JMS behavior for the default "AUTO_ACKNOWLEDGE" mode:
* that is, automatic message acknowledgment after listener execution,
* with redelivery in case of an exception thrown.
* with no redelivery in case of a user exception thrown but potential
* redelivery in case of the JVM dying during listener execution.
*
* <p>For a different style of MessageListener handling, through looped
* {@code MessageConsumer.receive()} calls that also allow for

41
src/asciidoc/integration.adoc

@ -1854,11 +1854,11 @@ takes a reference to a standard `ConnectionFactory` that would typically come fr @@ -1854,11 +1854,11 @@ takes a reference to a standard `ConnectionFactory` that would typically come fr
===== CachingConnectionFactory
The `CachingConnectionFactory` extends the functionality of `SingleConnectionFactory`
and adds the caching of Sessions, MessageProducers, and MessageConsumers. The initial
cache size is set to 1, use the property `SessionCacheSize` to increase the number of
cache size is set to 1, use the property `sessionCacheSize` to increase the number of
cached sessions. Note that the number of actual cached sessions will be more than that
number as sessions are cached based on their acknowledgment mode, so there can be up to
4 cached session instances when `SessionCacheSize` is set to one, one for each
`AcknowledgementMode`. MessageProducers and MessageConsumers are cached within their
4 cached session instances when `sessionCacheSize` is set to one, one for each
acknowledgment mode. MessageProducers and MessageConsumers are cached within their
owning session and also take into account the unique properties of the producers and
consumers when caching. MessageProducers are cached based on their destination.
MessageConsumers are cached based on a key composed of the destination, selector,
@ -1944,17 +1944,29 @@ to runtime demands or for participation in externally managed transactions. @@ -1944,17 +1944,29 @@ to runtime demands or for participation in externally managed transactions.
Compatibility-wise, it stays very close to the spirit of the standalone JMS
specification - but is generally not compatible with Java EE's JMS restrictions.
[NOTE]
====
While `SimpleMessageListenerContainer` does not allow for the participation in externally
managed transactions, it does support native JMS transactions: simply switch the
'sessionTransacted' flag to 'true' or, in the namespace, set the 'acknowledge' attribute
to 'transacted': Exceptions thrown from your listener will lead to a rollback then, with
the message getting redelivered. Alternatively, consider using 'CLIENT_ACKNOWLEDGE' mode
which provides redelivery in case of an exception as well but does not use transacted
Sessions and therefore does not include any other Session operations (such as sending
response messages) in the transaction protocol.
====
[[jms-mdp-default]]
===== DefaultMessageListenerContainer
This message listener container is the one used in most cases. In contrast to
`SimpleMessageListenerContainer`, this container variant does allow for dynamic adaption
`SimpleMessageListenerContainer`, this container variant allows for dynamic adaptation
to runtime demands and is able to participate in externally managed transactions. Each
received message is registered with an XA transaction when configured with a
`JtaTransactionManager`; so processing may take advantage of XA transaction semantics.
This listener container strikes a good balance between low requirements on the JMS
provider, advanced functionality such as transaction participation, and compatibility
with Java EE environments.
provider, advanced functionality such as the participation in externally managed
transactions, and compatibility with Java EE environments.
The cache level of the container can be customized. Note that when no caching is enabled,
a new connection and a new session is created for each message reception. Combining this
@ -1966,6 +1978,21 @@ a simple `BackOff` implementation retries every 5 seconds. It is possible to spe @@ -1966,6 +1978,21 @@ a simple `BackOff` implementation retries every 5 seconds. It is possible to spe
a custom `BackOff` implementation for more fine-grained recovery options, see
`ExponentialBackOff` for an example.
[NOTE]
====
Like its sibling `SimpleMessageListenerContainer`, `DefaultMessageListenerContainer`
supports native JMS transactions and also allows for customizing the acknowledgment mode.
This is strongly recommended over externally managed transactions if feasible for your
scenario: that is, if you can live with occasional duplicate messages in case of the
JVM dying. Custom duplicate message detection steps in your business logic may cover
such situations, e.g. in the form of a business entity existence check or a protocol
table check. Any such arrangements will be significantly more efficient than the
alternative: wrapping your entire processing with an XA transaction (through configuring
your `DefaultMessageListenerContainer` with an `JtaTransactionManager`), covering the
reception of the JMS message as well as the execution of the business logic in your
message listener (including database operations etc).
====
[[jms-tx]]
==== Transaction management
@ -1991,7 +2018,7 @@ use of a JTA transaction manager as well as a properly XA-configured ConnectionF @@ -1991,7 +2018,7 @@ use of a JTA transaction manager as well as a properly XA-configured ConnectionF
Reusing code across a managed and unmanaged transactional environment can be confusing
when using the JMS API to create a `Session` from a `Connection`. This is because the
JMS API has only one factory method to create a `Session` and it requires values for the
transaction and acknowledgement modes. In a managed environment, setting these values is
transaction and acknowledgment modes. In a managed environment, setting these values is
the responsibility of the environment's transactional infrastructure, so these values
are ignored by the vendor's wrapper to the JMS Connection. When using the `JmsTemplate`
in an unmanaged environment you can specify these values through the use of the

Loading…
Cancel
Save