From e5a2b348298ee9fd890bd61cbcf9a70d8bf4a278 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 27 Jul 2015 15:32:18 +0200 Subject: [PATCH] Clarify acknowledge mode semantics and transaction recommendations Issue: SPR-13278 --- .../AbstractMessageListenerContainer.java | 76 +++++++++++-------- .../SimpleMessageListenerContainer.java | 3 +- src/asciidoc/integration.adoc | 41 ++++++++-- 3 files changed, 79 insertions(+), 41 deletions(-) diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java index d2b5cf787d..ec36bcc06f 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/AbstractMessageListenerContainer.java +++ b/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; /** - * 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. * - *

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. + *

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. * - *

NOTE: The default behavior of this message listener container - * is to never propagate an exception thrown by a message listener up to - * the JMS provider. Instead, it will log any such exception at the error level. + *

NOTE: The default behavior of this message listener container is to + * never 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; *

  • "sessionAcknowledgeMode" set to "AUTO_ACKNOWLEDGE" (default): * This mode is container-dependent: For {@link DefaultMessageListenerContainer}, * it means automatic message acknowledgment before 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 after 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. + *
  • "sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE": + * Lazy 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. *
  • "sessionAcknowledgeMode" set to "CLIENT_ACKNOWLEDGE": * Automatic message acknowledgment after successful listener execution; - * best-effort redelivery in case of exception thrown. - *
  • "sessionAcknowledgeMode" set to "DUPS_OK_ACKNOWLEDGE": - * Lazy message acknowledgment during or after listener execution; - * potential redelivery 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). *
  • "sessionTransacted" set to "true": * Transactional acknowledgment after successful listener execution; - * guaranteed redelivery in case of exception thrown. + * guaranteed redelivery in case of a user exception thrown as well as + * in case of other listener execution interruptions (such as the JVM dying). * - * The exact behavior might vary according to the concrete listener container - * and JMS provider used. * - *

    There are two solutions to the duplicate processing problem: + *

    There are two solutions to the duplicate message processing problem: *

    * 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; *
  • 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 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 */ @SuppressWarnings("serial") private static class MessageRejectedWhileStoppingException extends RuntimeException { - } } diff --git a/spring-jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java b/spring-jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java index b5272f720b..c784eb5a12 100644 --- a/spring-jms/src/main/java/org/springframework/jms/listener/SimpleMessageListenerContainer.java +++ b/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 * 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. * *

    For a different style of MessageListener handling, through looped * {@code MessageConsumer.receive()} calls that also allow for diff --git a/src/asciidoc/integration.adoc b/src/asciidoc/integration.adoc index 7897cc3efb..ff2739f574 100644 --- a/src/asciidoc/integration.adoc +++ b/src/asciidoc/integration.adoc @@ -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. 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 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 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