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;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
/** /**
* Abstract base class for message listener containers. Can either host * Abstract base class for Spring message listener container implementations.
* a standard JMS {@link javax.jms.MessageListener} or a Spring-specific * Can either host a standard JMS {@link javax.jms.MessageListener} or Spring's
* {@link SessionAwareMessageListener}. * {@link SessionAwareMessageListener} for actual message processing.
* *
* <p>Usually holds a single JMS {@link Connection} that all listeners are * <p>Usually holds a single JMS {@link Connection} that all listeners are supposed
* supposed to be registered on, which is the standard JMS way of managing * to be registered on, which is the standard JMS way of managing listener sessions.
* listeners. Can alternatively also be used with a fresh Connection per * Can alternatively also be used with a fresh Connection per listener, for Java EE
* listener, for J2EE-style XA-aware JMS messaging. The actual registration * style XA-aware JMS messaging. The actual registration process is up to concrete
* process is up to concrete subclasses. * subclasses.
* *
* <p><b>NOTE:</b> The default behavior of this message listener container * <p><b>NOTE:</b> The default behavior of this message listener container is to
* is to <b>never</b> propagate an exception thrown by a message listener up to * <b>never</b> propagate an exception thrown by a message listener up to the JMS
* the JMS provider. Instead, it will log any such exception at the error level. * 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 * This means that from the perspective of the attendant JMS provider no such
* listener will ever fail. However, if error handling is necessary, then * listener will ever fail. However, if error handling is necessary, then
* any implementation of the {@link ErrorHandler} strategy may be provided to * any implementation of the {@link ErrorHandler} strategy may be provided to
@ -62,37 +62,48 @@ import org.springframework.util.ReflectionUtils;
* <li>"sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default): * <li>"sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default):
* This mode is container-dependent: For {@link DefaultMessageListenerContainer}, * This mode is container-dependent: For {@link DefaultMessageListenerContainer},
* it means automatic message acknowledgment <i>before</i> listener execution, with * 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 * it means automatic message acknowledgment <i>after</i> listener execution, with
* redelivery in case of an exception thrown, as defined by the JMS specification. * no redelivery in case of a user exception thrown but potential redelivery in case
* In order to consistently achieve the latter behavior with any container variant, * of the JVM dying during listener execution. In order to consistently arrange for
* consider setting "sessionTransacted" to "true" instead. * 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": * <li>"sessionAcknowledgeMode" set to "CLIENT_ACKNOWLEDGE":
* Automatic message acknowledgment <i>after</i> successful listener execution; * Automatic message acknowledgment <i>after</i> successful listener execution;
* best-effort redelivery in case of exception thrown. * best-effort redelivery in case of a user exception thrown as well as in case
* <li>"sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE": * of other listener execution interruptions (such as the JVM dying).
* <i>Lazy</i> message acknowledgment during or after listener execution;
* <i>potential redelivery</i> in case of exception thrown.
* <li>"sessionTransacted" set to "true": * <li>"sessionTransacted" set to "true":
* Transactional acknowledgment after successful listener execution; * 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> * </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> * <ul>
* <li>Either add <i>duplicate message detection</i> to your listener, in the * <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 * 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 * usually just needs to be done in case of the JMSRedelivered flag being
* set on the incoming message (else just process straightforwardly). * set on the incoming message (otherwise just process straightforwardly).
* <li>Or wrap the <i>entire processing with an XA transaction</i>, covering the * Note that with "sessionTransacted" set to "true", duplicate messages will
* reception of the message as well as the execution of the message listener. * only appear in case of the JVM dying at the most unfortunate point possible
* This is only supported by {@link DefaultMessageListenerContainer}, through * (i.e. after your business logic executed but before the JMS part got committed),
* specifying a "transactionManager" (typically a * 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 * {@link org.springframework.transaction.jta.JtaTransactionManager}, with
* a corresponding XA-aware JMS {@link javax.jms.ConnectionFactory} passed in as * a corresponding XA-aware JMS {@link javax.jms.ConnectionFactory} passed in
* "connectionFactory"). * as "connectionFactory").
* </ul> * </ul>
* Note that XA transaction coordination adds significant runtime overhead, * Note that XA transaction coordination adds significant runtime overhead,
* so it might be feasible to avoid it unless absolutely necessary. * so it might be feasible to avoid it unless absolutely necessary.
@ -108,7 +119,7 @@ import org.springframework.util.ReflectionUtils;
* <li>Alternatively, specify a * <li>Alternatively, specify a
* {@link org.springframework.transaction.jta.JtaTransactionManager} as * {@link org.springframework.transaction.jta.JtaTransactionManager} as
* "transactionManager" for a fully XA-aware JMS provider - typically when * "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 * transaction manager present. This will give full "exactly-once" guarantees
* without custom duplicate message checks, at the price of additional * without custom duplicate message checks, at the price of additional
* runtime processing overhead. * runtime processing overhead.
@ -921,7 +932,7 @@ public abstract class AbstractMessageListenerContainer extends AbstractJmsListen
if (errorHandler != null) { if (errorHandler != null) {
errorHandler.handleError(ex); errorHandler.handleError(ex);
} }
else if (logger.isWarnEnabled()) { else {
logger.warn("Execution of JMS message listener failed, and no ErrorHandler has been set.", ex); 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
*/ */
@SuppressWarnings("serial") @SuppressWarnings("serial")
private static class MessageRejectedWhileStoppingException extends RuntimeException { 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;
* on acknowledge modes and transaction options. Note that this container * on acknowledge modes and transaction options. Note that this container
* exposes standard JMS behavior for the default "AUTO_ACKNOWLEDGE" mode: * exposes standard JMS behavior for the default "AUTO_ACKNOWLEDGE" mode:
* that is, automatic message acknowledgment after listener execution, * 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 * <p>For a different style of MessageListener handling, through looped
* {@code MessageConsumer.receive()} calls that also allow for * {@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
===== CachingConnectionFactory ===== CachingConnectionFactory
The `CachingConnectionFactory` extends the functionality of `SingleConnectionFactory` The `CachingConnectionFactory` extends the functionality of `SingleConnectionFactory`
and adds the caching of Sessions, MessageProducers, and MessageConsumers. The initial 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 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 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 4 cached session instances when `sessionCacheSize` is set to one, one for each
`AcknowledgementMode`. MessageProducers and MessageConsumers are cached within their acknowledgment mode. MessageProducers and MessageConsumers are cached within their
owning session and also take into account the unique properties of the producers and owning session and also take into account the unique properties of the producers and
consumers when caching. MessageProducers are cached based on their destination. consumers when caching. MessageProducers are cached based on their destination.
MessageConsumers are cached based on a key composed of the destination, selector, 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.
Compatibility-wise, it stays very close to the spirit of the standalone JMS 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. 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]] [[jms-mdp-default]]
===== DefaultMessageListenerContainer ===== DefaultMessageListenerContainer
This message listener container is the one used in most cases. In contrast to 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 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 received message is registered with an XA transaction when configured with a
`JtaTransactionManager`; so processing may take advantage of XA transaction semantics. `JtaTransactionManager`; so processing may take advantage of XA transaction semantics.
This listener container strikes a good balance between low requirements on the JMS This listener container strikes a good balance between low requirements on the JMS
provider, advanced functionality such as transaction participation, and compatibility provider, advanced functionality such as the participation in externally managed
with Java EE environments. transactions, and compatibility with Java EE environments.
The cache level of the container can be customized. Note that when no caching is enabled, 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 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
a custom `BackOff` implementation for more fine-grained recovery options, see a custom `BackOff` implementation for more fine-grained recovery options, see
`ExponentialBackOff` for an example. `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]] [[jms-tx]]
==== Transaction management ==== Transaction management
@ -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 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 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 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 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` 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 in an unmanaged environment you can specify these values through the use of the

Loading…
Cancel
Save