8.2. JMS(Java Message Service)

8.2.1. Overview

This chapter explains how to send and receive messages which use components for JMS linking of JMS API and Spring Framework.


8.2.1.1. What is JMS

JMS is a standard API for using MOM (Message Oriented Middleware) in Java.
JMS architecture exchanges messages from client to client through JMS provider.
Since JMS supports asynchronous messaging, a close coupling can be formed between the clients.
Further, since the message can be stored in Queue by adopting a Point-to-Point model described later, messages can be received in accordance with the client performance.
On the other hand, since the time lag is likely to occur in the messaging between clients, it is not suitable for the processes which require a real time response.
For details of JMS, refer Java Message Service (JMS).

Asynchronous and synchronous messaging can be performed by using JMS.

Note

Use of JMS1.1 is assumed in this guideline.


While using, the delivery method and the method to send and receive messages described below must be selected in accordance with the requirements.
  • Delivery model
Delivery model consists of two models - Point-to-Point (PTP) and Publisher-Subscriber (Pub/Sub).
A major difference between two models is in whether the sender and recipient ratio is 1:1 or 1:many, and should be selected based on the use.
  1. Point-To-Point (PTP) model
JMS Queue
PTP model consists of 2 clients wherein one of the clients (Producer) sends a message and the other client (Consumer) alone receives that message.
Destination of the message in PTP model is called as a Queue.
Producer sends the message to the Queue and the Consumer fetches the message from the Queue, and the process is carried out.
Message is fetched from the consumer or when the message reaches expiry period, the message is deleted from the Queue.

  1. Publisher-Subscriber (Pub/Sub) model
JMS Topic
Pub/Sub model consists of 2 clients wherein one of the clients (Publisher) issues (Publishes) the message and delivers that message to multiple other clients (Subscribers).
Destination of the message in Pub/Sub model is called as a Topic.
Subscriber raises a subscription request for the Topic and the Publisher issues a message in Topic.
The message is delivered to all the subscribers who have raised a request for subscription.
This guideline explains how to implement PTP model which is widely used in general.
  • A method to send messages
There are two types of processes for message sending - Synchronous transmission method and asynchronous transmission method for sending the messages to Queue or Topic however JMS1.1 supports only synchronous transmission method.
  1. Synchronous transmission method
Message is processed and sent by explicitly calling a function to send messages.
Subsequent processing is blocked in order to wait until the response is received from JMS provider.

  1. Asynchronous transmission method
Message is processed and sent by explicitly calling a function to send messages.
Subsequent processing is continued since it is not necessary to wait for the response from JMS provider.
For the details of asynchronous transmission method, refer Java Message Service(Version 2.0)”7.3. Asynchronous send”.
  • Message reception method
There are two types of methods for receiving messages - synchronous reception method and asynchronous reception method while implementing the process for receiving messages in Queue or Topic.
As described later, since use cases of synchronous reception methods are limited, generally asynchronous reception methods are widely used.
  1. Asynchronous reception method
When the message is received by Queue or Topic, the processing for the received message is initiated.
Since the processing for one message is initiated without terminating the processing for the other message, it is suitable for parallel processing.

  1. Synchronous reception method
Receiving the message and related processing is initiated by explicitly calling the function which receives the message.
The function which receives the message waits till the message is received when the message does not exist in Queue or Topic.
Hence, the waiting period for the message must be specified by configuring the timeout value.
As an example of synchronous reception of message, it can be used when the message accumulated in the Queue, in the Web application is to be fetched and processed at any time like screen operation etc or when the messages are to be processed periodically in a batch.

In JMS, the message consists of following parts.
For details, refer “3. JMS Message Model” of Java Message Service(Version 1.1).
Structure Description
Header
Control information of message like destination and identifier, extension header (JMSX) of JMS, JMS provider specific header and application specific header are stored for JMS provider and application.
Property
Control information to be added to header is stored.
Payload
Message body is stored.
As data types, 5 types of message types namely javax.jms.BytesMessage, javax.jms.MapMessage, javax.jms.ObjectMessage, javax.jms.StreamMessageand javax.jms.TextMessageare offered.
ObjectMessage is used while sending a JavaBean.
In such a case, JavaBean must be shared between the clients.

8.2.1.2. Using JMS

When the process is to be implemented by using JMS, it can be executed by using JMS API defined in Java EE (hereafter referred to as JMS API).
However, this guideline assumes the use of JMS linking component of Spring Framework wherein the merits are significant as compared to using JMS API as it is (easy description etc).
Hence, details of JMS API are not described.
For details, refer Java API.

Note

For JMS, Java API is standardized however physical protocols of messages are not standardized.

Note

Since JMS implementation is incorporated in Java EE server as a standard, it can be used as a default (restricted while using JMS provider incorporated in Java EE server), however JMS must be separately implemented in Java EE server like Apache Tomcat wherein JMS is not incorporated.



8.2.1.3. Using JMS which use component of Spring Framework

Following are provided as the libraries for sending and receiving messages in Spring Framework.
  • spring-jms
    It offers a component for the messaging which use JMS.
    By using the components included in this library, low level JMS API calling becomes unnecessary and the implementation becomes simplified.
    spring-messaging can be used.
  • spring-messaging
    It offers a component to abstract the infrastructure function which is required for creating application of messaging base.
    It consists of a set of annotations to associate with the message and the method for processing the same.
    By using the components included in the library, implementation style of messaging can be matched.
Although implementation can be done only in spring-jms, implementation method can be combined by using spring-messaging.
This guideline recommends the use of spring-messaging as well.
Here, prior to explaining basic implementation method, it is explained how the messages are sent and received by the components for JMS linking offered by Spring Framework.
At first, the components registered in the description are introduced.
Spring Framework sends and receives messages through JMS API using interfaces and classes shown below.
  • javax.jms.ConnectionFactory
    An interface for creating a connection with JMS provider.
    It offers a function to create a connection to JMS provider from the application.
  • javax.jms.Destination
    An interface for indicating the address (Queue or topic).
  • javax.jms.MessageProducer
    An interface for sending messages.
  • javax.jms.MessageConsumer
    An interface for receiving messages.
  • javax.jms.Message
    An interface which shows the message that retains header and body.
    Messages are sent and received by implementation class of the interface.
  • org.springframework.messaging.Message
    An interface which abstracts messages handled by various messaging systems.
    It can also be used in JMS.
    As mentioned earlier, basically org.springframework.messaging.Message offered by spring-messaging is used to comply with implementation method of messaging.
    However, javax.jms.Message is used when it is preferable to use org.springframework.jms.core.JmsTemplate.
  • org.springframework.jms.core.JmsMessagingTemplate and org.springframework.jms.core.JmsTemplate
    A class which creates templates for generation and release of resources for using JMS API.
    The implementation can be simplified by using a message transmission and message synchronous reception function.
    Basically, JmsMessagingTemplate which can handle org.springframework.messaging.Message is used.
    Since JmsMessagingTemplate wraps JmsTemplate, configuration can be done by using JmsTemplate property.
    However, sometimes it is preferable to use a JmsTemplate as it is. Specific use cases are explained later.
  • org.springframework.jms.listener.DefaultMessageListenerContainer
    DefaultMessageListenerContainer receives messages from Queue and initiates MessageListener which processes the received messages.
  • @org.springframework.jms.annotation.JmsListener
    A marker annotation which indicates that it is a method to be handled as MessageListener of JMS.
    A @JmsListener annotation is assigned for a method which performs a process while receiving messages.
  • org.springframework.jms.connection.JmsTransactionManager
    An implementation class to manage transactions by calling API of JMS(javax.jms.Connection/ javax.jms.Session).

8.2.1.3.1. When messages are sent synchronously

The flow of the process to send messages synchronously is explained using the figure.
Send of Spring JMS
Sr.No. Description
(1)
Execute the process in Service by passing “Destination name for sending” and “payload of messages to be sent” for JmsMessagingTemplate.
JmsMessagingTemplate delegates the process to JmsTemplate.
(2)
JmsTemplatefetches javax.jms.Connection from ConnectionFactory fetched through JNDI.
(3)
JmsTemplate passes Destination and messages to MessageProducer.
MessageProducer is generated from javax.jms.Session. (Session is generated from Connection fetched in (2).)
Further, Destination is fetched through JNDI based on “Destination name for sending” passed in (1).
(4)
MessageProducer sends messages to Destination for sending.

8.2.1.3.2. When messages are received asynchronously

The flow of the process to receive messages asynchronously is explained using a figure.
ASync of Spring JMS
Sr. No. Description
(1)
Fetch Connection from ConnectionFactory fetched through JNDI.
(2)
DefaultMessageListenerContainer passes Destination to MessageConsumer.
MessageConsumer is generated from Session. (Session is generated from Connection fetched in (1))
Further, Destination is fetched through JNDI based on “Destination name for receiving” specified in @JmsListener annotation.
(3)
MessageConsumer receives messages from Destination.
(4)
A method (listener method) configured by @JmsListener annotation in MessageListener is called using received message as an argument. Listener method is managed by DefaultMessageListenerContainer.

8.2.1.3.3. When the messages are received synchronously

The flow of the process to receive messages synchronously is explained using a figure.
Sync of Spring JMS
Sr. No. Description
(1)
Pass “Destination name for receiving” in Service for JmsMessagingTemplate.
JmsMessagingTemplate delegates the process to JmsTemplate.
(2)
JmsTemplate fetches Connection from ConnectionFactory fetched through JNDI.
(3)
JmsTemplate passes Destination and messages in MessageConsumer.
MessageConsumer is generated from Session. (Session is generated from Connection fetched in (2).)
Further, Destination is fetched through JNDI based on “Destination name for receiving” passed in (1).
(4)
MessageConsumer receives the messages from Destination.
Message is returned to the Service through JmsTemplate or JmsMessagingTemplate.

8.2.1.4. Regarding project configuration

Recommended configuration of the project while using JMS is explained.
When serialised JavaBean is sent and received through ObjectMessage, the JavaBean must be shared by sending side and receiving side.
In such a case, it is recommended to add a model project different from the existing blank project.
  • Sharing model
  • When client of sending and receiving side does not provide a model

    model project is added and Jar file is distributed in the client for communication.

  • When client of sending and receiving side offer a model

    The model provided is added to library.

model project, and, relation between distributed archive file and existing project are as below.
Projects
Sr. No. Project name Description
(1)
web project
Place the listener class to receive messages asynchronously.
(2)
domain project
Place the Service executed from listener class for receiving messages asynchronously.
Besides, Repository etc are same as used conventionally.
(3)
model project or Jar file
A class which is shared between the clients is used among the classes belonging to domain layer.

Following are implemented to add a model project.
  • Creating a model project
  • Adding a dependency from domain project to model project
For detail addition method, refer Project configuration is changed for SOAP server. of SOAP Web Service (Server/Client). shared by JavaBean in the same way.

8.2.2. How to use

8.2.2.1. Settings that are common for both sending and receiving messages

This section explains common settings required for sending and receiving messages.

8.2.2.1.1. Configuration of dependent library

spring-jms of Spring Framework is added to pom.xml of domain project in order to use component for linking JMS of Spring Framework.
  • [projectName]-domain/pom.xml
<dependencies>

     <!-- (1) -->
     <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-jms</artifactId>
     </dependency>

 </dependencies>
Sr. No. Description
(1)
Add dependencies to spring-jms.
Since spring-jms is dependent on spring-messaging, spring-messaging is also added as a transitive dependent library.
A library of JMS provider is added to pom.xml in addition to spring-jms.
For additional examples of libraries for pom.xml, refer JMS provider dependent configuration.

Note

In the above setting example, since it is assumed that the dependent library version is managed by the parent project terasoluna-gfw-parent , specifying the version in pom.xml is not necessary. The above dependent library used by terasoluna-gfw-parent is defined by Spring IO Platform.


8.2.2.1.2. Configuration of ConnectionFactory

ConnectionFactory definition method consists of two methods - a method defined by application server and a method defined by Bean definition file.
A method defined by application server is selected to make Bean definition file independent of JMS provider unless there is a specific reason to do otherwise.
This chapter explains a method defined by application server only.
Configuration for using a JavaBean fetched in Bean definition file through JNDI must be performed in order to use ConnectionFactory defined in the application server.
  • [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<!-- (1) -->
<jee:jndi-lookup id="connectionFactory" jndi-name="jms/ConnectionFactory"/>
Sr. No. Description
(1)
Specify JNDI name of ConnectionFactory offered by application server, in jndi-name attribute.
Since resource-ref attribute is set to true by default, it is auto-assigned when no prefix exists for JNDI name (java:comp/env/).

Note

When ConnectionFactory for which a Bean is defined is used

When JNDI is not used, ConnectionFactory can be used even when a Bean is defined for implementation class of ConnectionFactory. In such a case, implementation class of ConnectionFactory is dependent on JMS provider. For details, refer JMS provider dependent configuration “configuration when JNDI is not used”.

8.2.2.1.3. Configuration of DestinationResolver

Name resolution of Destination consists of 2 methods - Resolution by JNDI and Resolution by JMS provider.
Resolution by JMS provider is performed by default, however resolution by JNDI is recommended for portability and control if there are no particular issues to address.
Name resolution of Destination can be performed by JNDI name, by using org.springframework.jms.support.destination.JndiDestinationResolver.
How to define JndiDestinationResolver is shown below.
  • [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<!-- (1) -->
<bean id="destinationResolver"
   class="org.springframework.jms.support.destination.JndiDestinationResolver">
   <property name="resourceRef" value="true" /> <!-- (2) -->
</bean>
Sr. No. Description
(1)
Define a Bean for JndiDestinationResolver.
(2)
When there is no prefix in JNDI name (java:comp/env/), set truewhen it is auto-assigned. Default value is false.

Warning

Note that resource-ref attribute of <jee:jndi-lookup/> is different from the default value.

8.2.2.2. A method to synchronously send messages

A method to synchronously send messages from client (Producer) to JMS provider, for PTP model is explained below.

8.2.2.2.1. Basic synchronous transmission

Process of synchronous transmission to JMS provider is achieved by using JmsMessagingTemplate.
An implementation example wherein object of Todo class is synchronously sent as a message.
At first, how to configure JmsMessagingTemplate is shown below.
  • [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<bean id="cachingConnectionFactory"
   class="org.springframework.jms.connection.CachingConnectionFactory"> <!-- (1) -->
   <property name="targetConnectionFactory" ref="connectionFactory" /> <!-- (2) -->
   <property name="sessionCacheSize" value="1" />  <!-- (3) -->
</bean>

<!-- (4) -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
   <property name="connectionFactory" ref="cachingConnectionFactory" />
   <property name="destinationResolver" ref="destinationResolver" />
</bean>

<!-- (5) -->
<bean id="jmsMessagingTemplate" class="org.springframework.jms.core.JmsMessagingTemplate">
    <property name="jmsTemplate" ref="jmsTemplate"/>
</bean>
Sr. No. Description
(1)
Define a Bean for org.springframework.jms.connection.CachingConnectionFactory which performs caching of Session and MessageProducer/Consumer.
JMS provider specific ConnectionFactory looked up by Bean definition or JNDI name is not used as it is. Cache function can be used by wrapping it in CachingConnectionFactory.
(2)
Specify JMS provider specific ConnectionFactory which is looked up by Bean definition or JNDI name.
(3)
Specify cache count for Session. (Default value is 1)
Although 1 is specified in this example, number of caches must be changed appropriately corresponding to performance requirements.
When the session is required to be continued even after exceeding cache count, a new session is created and destroyed repeatedly without using a cache.
This is likely to cause deterioration of process efficiency resulting in performance degradation.
(4)
Define a Bean for JmsTemplate.
JmsTemplate alternates for a low level API handling (JMS API calling).
For attributes that can be configured, refer list of attributes of JmsTemplate given below.
(5)
Define a Bean for JmsMessagingTemplate. Inject JmsTemplate which alternates as a synchronous transmission process.
Attributes of JmsTemplate for synchronous transmission are as below.
It must be configured as per requirement.
Sr. No. Configuration item Details Mandatory Default value
connectionFactory
Configure ConnectionFactory to be used.
Nil (since it is mandatory)
pubSubDomain
Configure for a messaging model.
Set false for PTP (Queue) model and set true for Pub/Sub (Topic).
- false
sessionTransacted
Specify whether the transaction control is performed in the session.
In this guideline, false is recommended as a default for using transaction control described later.
- false
messageConverter
Specify message converter.
Default can be used for the scope described in the guideline.
- SimpleMessageConverter(*1) is used.
destinationResolver
Specify DestinationResolver.
In this guideline, it is recommended to use JndiDestinationResolver which performs name resolution in JNDI.
-
DynamicDestinationResolver(*2) is used.
(When DynamicDestinationResolver is used, name resolution of Destination is performed by JMS provider.)
defaultDestination
Specify existing Destination.
When Destination is not specified explicitly, this Destination is used.
- null (no existing destination)
deliveryMode
Select 1 (NON_PERSISTENT) or 2(PERSISTENT) for delivery mode.
2(PERSISTENT) performs perpetuation of messages.
1(NON_PERSISTENT) does not perform perpetuation of messages.
Hence, even though performance shows improvement, messages are likely to get lost if JMS provider is restarted.
In this guideline, it is recommended to use 2 (PERSISTENT) for avoiding loss of messages.
When this configuration is used, note that explicitQosEnabled described later must be set to true.
- 2(PERSISTENT)
priority
Set priority for messages. Priority can be set from 0 to 9.
Higher the number, higher is the priority.
Priority is evaluated when the message is stored in the Queue at the time of synchronous transmission. Messages are stored in such a way that messages with high priority are extracted before the messages with low priority.
FIFO (First-In-First-Out) system is employed for the messages with identical priorities.
When this configuration is used, note that explicitQosEnabled described later must be set to true.
- 4
timeToLive
Specify validity period of messages in milliseconds.
When the message reaches the validity period, JMS provider deletes the message from Queue
When this configuration is used, note that explicitQosEnabled described later must be set to true.
- 0 (Unrestricted)
explicitQosEnabled
Specify true when deliveryMode, priority and timeToLive are enabled.
- false

(*1)org.springframework.jms.support.converter.SimpleMessageConverter

(*2)org.springframework.jms.support.destination.DynamicDestinationResolver


Next, a JavaBean is created for sending.
  • [projectName]-domain/src/main/java/com/example/domain/model/Todo.java
package com.example.domain.model;

import java.io.Serializable;

public class Todo implements Serializable { // (1)

    private static final long serialVersionUID = -1L;

    // omitted

    private String description;

    // omitted

    private boolean finished;

    // omitted

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isFinished() {
        return finished;
    }

    public void setFinished(boolean finished) {
        this.finished = finished;
    }

}
Sr. No. Description
(1)
Basically, a normal JavaBean can be used, however, a java.io.Serializable interface must be implemented for a serialised transmission.
A process to perform actual synchronous transmission is described in the end.
An implementation example is shown below wherein Todo object consisting of specified text is synchronously sent to Queue.
  • [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo;

import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import com.example.domain.model.Todo;

@Service
public class TodoServiceImpl implements TodoService {

    @Inject
    JmsMessagingTemplate jmsMessagingTemplate;    // (1)

    @Override
    public void sendMessage(String message) {

       Todo todo = new Todo();
       // omitted

       jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue", todo);  // (2)

    }
}
Sr. No. Description
(1)
Inject JmsMessagingTemplate.
(2)
By using convertAndSend method of JmsMessagingTemplate, JavaBean of argument is converted to implementation class of org.springframework.messaging.Message interface and the message is synchronously sent for a specified Destination.
org.springframework.jms.support.converter.SimpleMessageConverter is used in the default conversion.
When SimpleMessageConverter is used, the class which implements javax.jms.Message, java.lang.String, byte array, java.util.Map and java.io.Serializable interface can be sent.

Note

Exception handling of JMS in business logic

As covered in JMS (Java Message Service) introduction, exceptions are converted to run-time exceptions in Spring Framework. Hence, a run-time exception must be handled while handling JMS exception in business logic.

Template class A method to convert exceptions Exception after conversion
JmsMessagingTemplate
convertJmsException method of JmsMessagingTemplate
MessagingException(*1) and its sub-exception
JmsTemplate
convertJmsAccessException method of JmsAccessor
JmsException(*2) and its sub-exception

(*1) org.springframework.messaging.MessagingException

(*2) org.springframework.jms.JmsException


8.2.2.2.2. When message header is to be edited and sent synchronously

Header attribute can be edited and then sent synchronously by specifying header attribute and value of Key-Value format in convertAndSend method argument of JmsMessagingTemplate. For header details, refer javax.jms.Messages. An implementation example wherein JMSCorrelationID of role which links sending and response messages is specified at the time of synchronous transmission.

  • [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo;

import java.util.Map;
import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.jms.support.JmsHeaders;
import com.example.domain.model.Todo;

@Service
public class TodoServiceImpl implements TodoService {

@Inject
JmsMessagingTemplate jmsMessagingTemplate;

  public void sendMessageWithCorrelationId(String correlationId) {

    Todo todo = new Todo();
    // omitted

    Map<String, Object> headers = new HashMap<>();
    headers.put(JmsHeaders.CORRELATION_ID, correlationId);// (1)

    jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue",
            todo, headers); // (2)

  }
}
Sr. No. Description
(1)
Create header information by configuring header attribute name and its value, for implementation class of Map.
(2)
Synchronously send the message assigned with header information created in (2) by using convertAndSend method of JmsMessagingTemplate.

Warning

Regarding header attributes that can be edited

Components of header attributes (JMSDestination, JMSDeliveryMode, JMSExpiration, JMSMessageID, JMSPriority, JMSRedeliveredand JMSTimestamp) are handled as read-only while converting messages using SimpleMessageConverter of Spring Framework. Hence, even though read-only header attributes are configured as shown in the implementation example, they are not stored in the header of sent messages. (retained as message properties.) Among read-only header attributes, JMSDeliveryMode and JMSPriority can be configured in JmsTemplate unit. For details, refer list of attributes of JmsTemplate of Basic synchronous transmission.

8.2.2.2.3. Transaction control

When data consistency is required to be guaranteed, a transaction control function is used.
An implementation example wherein “Declaration type transaction control” is used is recommended in this guideline.
For details of “Declaration type transaction control”, refer Regarding transaction management .

org.springframework.jms.connection.JmsTransactionManager is used to achieve transaction control.
Configuration example is shown at first.
  • [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-domain.xml
<!-- (1) -->
<bean id="sendJmsTransactionManager"
   class="org.springframework.jms.connection.JmsTransactionManager">
   <!-- (2) -->
   <property name="connectionFactory" ref="cachingConnectionFactory" />
</bean>
Sr. No. Description
(1)
Define a Bean for JmsTransactionManager.

Note

Regarding bean name of TransactionManager

When @Transactional annotation is assigned, a Bean registered by Bean name transactionManager is used as a default. (For details, refer Settings for using transaction management )

In Blank project, since DataSourceTransactionManager is defined by Bean name called transactionManager, Bean is defined with an alias in the configuration above.

Hence, when only one TransactionManager is used in the application, specification of transactionManager attribute in @Transactional annotation can be omitted by using transactionManager as a Bean name.

(2)
Specify CachingConnectionFactory which controls a transaction.

An implementation example is shown below wherein Todo object is synchronously sent to Queue by performing transaction control.

  • [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo;

import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;

@Service
@Transactional("sendJmsTransactionManager")  // (1)
public class TodoServiceImpl implements TodoService {
   @Inject
   JmsMessagingTemplate jmsMessagingTemplate;

   @Override
   public void sendMessage(String message) {

      Todo todo = new Todo();
      // omitted

      jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue", todo);  // (2)
   }

}
Sr. No. Description
(1)
Declare transaction boundary by using @Transactional annotation.
Accordingly, transaction starts while starting each method in the class and transaction is committed while terminating a method.
(2)
Send message synchronously to Queue.
However, note that the message is actually sent to Queue within the timing wherein the transaction is committed.

In the application wherein DB transaction control is performed, a transaction control policy must be determined after reviewing relation between JMS and DB transaction based on business requirements.

A method which uses global transaction by JTA exists for linking JMS and DB transactions, however “Best Effort 1 Phase Commit” is recommended since overheads are likely to occur for performance, among protocol characteristics. Refer below for details.

Warning

When transaction process results are not returned in JMS provider due to issues like loss of connection with JMS provider after receiving a message

When transaction process results are not returned in JMS provider due to issues like loss of connection with JMS provider after receiving a message, transaction handling depends on JMS provider. Hence, a design considering loss of received messages must be carried out. Especially, when loss of messages is absolutely not permissible, providing a system to compensate for the loss of messages or using a global transaction etc must be adopted.

“Best Effort 1 Phase Commit” can be achieved by using org.springframework.data.transaction.ChainedTransactionManager.
An implementation example is shown below wherein sendJmsTransactionManager of Transaction control is used in JMS transaction management whereas transactionManager defined in default configuration of blank project is used in DB transaction management.
  • [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-env.xml
<!-- (1) -->
<bean id="sendChainedTransactionManager" class="org.springframework.data.transaction.ChainedTransactionManager">
    <constructor-arg>
        <list>
            <!-- (2) -->
            <ref bean="sendJmsTransactionManager" />
            <ref bean="transactionManager" />
        </list>
    </constructor-arg>
</bean>
Sr. No. Description
(1)
Define a Bean for ChainedTransactionManager.
(2)
Specify JMS and DB transaction manager.
Transaction starts in a registered sequence and transaction is committed in the reverse sequence.

Implementation example using the configuration given above is shown below.

  • [projectName]-domain/src/main/java/com/example/domain/service/todo/ChainedTransactionalTodoServiceImpl.java

    package com.example.domain.service.todo;
    
    import javax.inject.Inject;
    import org.springframework.jms.core.JmsMessagingTemplate;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    import com.example.domain.model.Todo;
    
    @Service
    @Transactional("sendChainedTransactionManager")  // (1)
    public class ChainedTransactionalTodoServiceImpl implements ChainedTransactionalTodoService {
       @Inject
       JmsMessagingTemplate jmsMessagingTemplate;
    
       @Inject
       TodoSharedService todoSharedService;
    
       @Override
       public void sendMessage(String message) {
           Todo todo = new Todo();
           // omitted
    
           jmsMessagingTemplate.convertAndSend("jms/queue/TodoMessageQueue", todo); // (2)
    
           // omitted
           todoSharedService.insert(todo); // (3)
       }
    
    }
    
    Sr. No. Description
    (1)
    Control JMS and DB transactions by specifying sendChainedTransactionManager in @Transactional annotation.
    For details of @Transactional annotation, refer Regarding transaction management of Domain Layer Implementation.
    (2)
    Send messages synchronously.
    (3)
    Execute process associated with DB access. In this example, SharedService is executed along with DB update.

    Note

    If it is necessary to manage multiple transactions together like JMS and DB, for business, a global transaction is considered. For global transactions, refer “when transaction control (global transaction control) is necessary for multiple DB (multiple resources)” of Settings for using transaction management.


8.2.2.3. A method to receive messages asynchronously

As described in “message reception method” of What is JMS, asynchronous reception is generally used for receiving messages.
Asynchronous reception can be achieved by registering a listener method assigned by @JmsListener annotation for DefaultMessageListenerContainer responsible for asynchronous reception function
Roles of listener method which perform s processing at the time of asynchronous reception are shown below.
  1. Provide a method to receive messages.
    Messages can be received by executing a method assigned by @JmsListener annotation.
  2. Call business process.
    Business process is not implemented by listener method. It is delegated to Service method.
  3. Handle exceptions occurring in business logic.
    Business exceptions and library exceptions occurred during normal operations are handled.
  4. Send process results as messages.
    In the methods wherein response messages are required to be sent, process results of listener method and business logic should be sent as messages for a specified Destination, by using org.springframework.jms.listener.adapter.JmsResponse.

8.2.2.3.1. Basic asynchronous reception

An asynchronous reception method using @JmsListener annotation is explained.
Following configuration is required for the implementation of asynchronous reception.
  • Define JMS Namespace.
  • Enable @JmsListener annotation.
  • Specify @JmsListener annotation in the method of component managed by DI container.
Respective detail implementation methods are described below.
  • [projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
<!-- (1) -->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jms="http://www.springframework.org/schema/jms"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd">

    <!-- (2) -->
    <jms:annotation-driven />


    <!-- (3) -->
    <jms:listener-container
        factory-id="jmsListenerContainerFactory"
        destination-resolver="destinationResolver"
        concurrency="1"/>
Sr. No. Attribute name Details
(1)
xmlns:jms
Define JMS Namespace.
Specify http://www.springframework.org/schema/jms as a value.
For details of JMS Namespace, refer JMS Namespace Support.
  xsi:schemaLocation
Specify URL of schema.
Add http://www.springframework.org/schema/jms and http://www.springframework.org/schema/jms/spring-jms.xsd to values.
(2)
-
Enable JMS related annotation function of @JmsListener annotation and @SendTo annotation by using <jms:annotation-driven />.
(3)
-
Configure DefaultMessageListenerContainer by assigning parameters to the factory which generate DefaultMessageListenerContainer, by using <jms:listener-container/>.
connection-factory attribute which can specify a Bean of ConnectionFactory to be used, exists in the <jms:listener-container/>attribute. Default value of connection-factory attribute is connectionFactory.
In this example, since Bean (Bean name is connectionFactory) of ConnectionFactory shown in Configuration of ConnectionFactory is used, connection-factory attribute is omitted.
<jms:listener-container/> also consists of attributes other than introduced here.

Warning

Since DefaultMessageListenerContainer is equipped with an independent cache function, CachingConnectionFactory should not be used in case of asynchronous receiving. For details, refer Javadoc of DefaultMessageListenerContainer. For above, ConnectionFactory defined in Configuration of ConnectionFactory should be specified in connection-factory attribute of <jms:listener-container/>.

  concurrency
Specify upper limit for parallel number of each listener method managed by DefaultMessageListenerContainer.
Default value for concurrency attribute is 1.
Lower limit and upper limit of parallel numbers can also be specified. For example, specify “5-10” when lower limit is 5 and upper limit is 10.
When the parallel number of listener method has reached specified upper limit, parallel processing is not done and a “waiting” state is reached.
A value should be specified as required.

Note

When you want to specify parallel number in listener method unit, concurrency attribute of @JmsListener annotation can be used.

  destination-resolver
Specify Bean name of DestinationResolver which is used to resolve Destination name at the time of asynchronous reception.
For Bean definition of DestinationResolver, refer Configuration of DestinationResolver.
When destination-resolver attribute is not specified, DynamicDestinationResolver generated in DefaultMessageListenerContainer is used.
  factory-id
Specify name of DefaultJmsListenerContainerFactory which defines a Bean.
Since @JmsListener annotation refers Bean name jmsListenerContainerFactory as a default, it is recommended to consider Bean name as jmsListenerContainerFactory in case of a single <jms:listener-container/>.
  cache
Specify cache level to determine cache targets like Connection, Session or MessageConsumer etc.
Select any of the connection, session, consumer, none(do not cache) and auto(select automatically).
cacheattribute is omitted since autois specified by default.

Note

If auto is specified, the behaviour changes depending on whether the transaction-manager attribute is specified. When it is specified, the behaviour is same as when noneis specified. When it is not specified, the behaviour is same as when consumer is specified. This is because transaction-manager attribute is specified only while using JTA transaction. When Connection or Session are not pooled in application server, specifying consumer should be considered for performance enhancement.

Messages can be asynchronously received from specified Destination by specifying @JmsListener annotation in the component method managed by DI container. Implementation method is as shown below.

  • [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
package com.example.listener.todo;

import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import com.example.domain.model.Todo;
@Component
public class TodoMessageListener {

   @JmsListener(destination = "jms/queue/TodoMessageQueue")   // (1)
   public void receive(Todo todo) {
      // omitted
   }

}
Sr. No. Description
(1)
Specify @JmsListener annotation for a method, for asynchronous reception. Specify Destination name for receiving in destination attribute.

A list of main attributes of @JmsListener annotation is shown below. For details and other attributes, refer Javadoc of @JmsListener annotation.

Sr. No. Fields Details
destination
Specify Destination to be received.
containerFactory
Specify Bean name of DefaultJmsListenerContainerFactorywhich manages the listener method.
Default is jmsListenerContainerFactory.
selector
Specify a message selector which acts as a condition for restricting the message to be received.
When the value is not explicitly specified, default is “”(blank character) and all the messages can be received.
concurrency
Specify upper limit of parallel numbers for listener method.;
Default value for concurrency attribute is 1.
Lower limit and upper limit of parallel numbers can be specified. For example, specify “5-10” when lower limit is 5 and upper limit is 10.
When the parallel number of listener method has reached specified upper limit, parallel processing is not done and a “waiting” state is reached.
Value should be specified as required.

8.2.2.3.2. Fetching header information of messages

When the header information of messages is to be used in the listener method such as sending process results of asynchronous reception to the Destination specified on Producer side (Value of header attribute JMSReplyTo), @org.springframework.messaging.handler.annotation.Header annotation is used.
Implementation example is shown below.
  • [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@JmsListener(destination = "jms/queue/TodoMessageQueue")
public JmsResponse<Todo> receiveAndResponse(
        Todo todo, @Header("jms_replyTo") Destination storeResponseMessageQueue) { // (1)

    // omitted

     return JmsResponse.forDestination(todo, storeResponseMessageQueue);
}
Sr. No. Description
(1)
Specify @Header annotation to fetch value of header attribute JMSReplyTo of receiving message.
For a key specified while fetching JMS standard header attribute, refer JmsHeaders constants definition.

8.2.2.3.3. Process results after asynchronous reception are sent as messages

A method which sends the process results of method defined in @JmsListener annotation, to Destination as a response message.
Following 2 methods can be listed for specifying transmission destination of process results.
  • When transmission destination of process results is specified statically
  • When transmission destination of process results is specified dynamically

Respective descriptions are as below.

  • When transmission destination of process results is specified statically
    Process results can be sent as messages to a fixed destination by defining @SendTo annotation which specifies a destination, for a method defined by @JmsListener annotation.
    Implementation example is as shown below.
  • [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@JmsListener(destination = "jms/queue/TodoMessageQueue")
@SendTo("jms/queue/ResponseMessageQueue") // (1)
public Todo receiveMessageAndSendTo(Todo todo) {

    // omitted
    return todo; // (2)
}
Sr. No. Description
(1)
Default transmission destination of process results can be specified by defining @SendTo annotation.
(2)
Return data to be sent to Destination defined in @SendTo annotation.
Permissible return value types are the classes which implement org.springframework.messaging.Message, javax.jms.Message, String, byte array, Map and Serializable interface.
  • When transmission destination of process results is changed dynamically
When transmission destination is to be changed dynamically, forDestination or forQueue methods of JmsResponse class are used
Process results can be sent to any Destination by dynamically changing Destination for sending or Destination name. Implementation example is as shown below.
  • [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@JmsListener(destination = "jms/queue/TodoMessageQueue")
public JmsResponse<Todo> receiveMessageJmsResponse(Todo todo) {

    // omitted

    String resQueue = null;

    if (todo.isFinished()) {
        resQueue = "jms/queue/FinihedTodoMessageQueue";
    } else {
        resQueue = "jms/queue/ActiveTodoMessageQueue";
    }

    return JmsResponse.forQueue(todo, resQueue); // (1)
}
Sr. No. Description
(1)
When Queue for transmission is to be changed in accordance with the process details, forDestination or forQueue methods of JmsResponse class are used.
In this example, messages are sent from Destination name by using forQueue method.

Note

When forQueue method of JmsResponse class is used, Destination name is used as a string. For resolving destination name, DestinationResolver specified in DefaultMessageListenerContainer is used.

Note

When transmission destination of process results is specified on Producer side

Using the implementation below, messages of process results can be sent to any destination specified on Producer side.

Implementation location Implementation details
Producer side
Specify Destination in header attribute JMSReplyTo of messages in accordance with JMS standards.
For editing of header attribute, refer When message header is to be edited and sent synchronously.
Consumer side
Return objects which send a message.

Header attribute JMSReplyTo is given priority over the default Destination specified on the Consumer side. For details, refer Response management.

8.2.2.3.4. When messages which are asynchronously received are to be restricted

The messages to be received can be restricted by specifying a message selector at the time of receiving.

  • [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@JmsListener(destination = "jms/queue/MessageQueue" , selector = "TodoStatus = 'deleted'")    // (1)
public void receive(Todo todo) {
    // omitted
}
Sr. No. Description
(1)
Conditions for receiving can be set by using selector attribute.
TodoStatus of header attribute receives only deleted messages.
Message selector is based on subset of SQL92 conditional expression syntax.
For details, refer Message Selectors.

8.2.2.3.5. Input check for the messages which are received asynchronously

An input check must be carried out to ensure that the messages which retain invalid data are not processed in the business logic from a security viewpoint.
Input check is implemented in Service method by using Method Validation and the exception at the time of input check error is handled by the listener method.
This is done to avoid the occurrence of unnecessary rollback process for the exception occurring at the time of input check error, while performing transaction control. For transaction control, refer Transaction control.
For details of how to configure and implement Method Validation, refer Method Validation of Input Validation.
An implementation example is shown below wherein input check is performed for Todo object shown in Basic synchronous transmission.
  • [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo;

import javax.validation.Valid;
import org.springframework.validation.annotation.Validated;
import com.example.domain.model.Todo;

@Validated // (1)
public interface TodoService {

    void updateTodo(@Valid Todo todo); // (2)

}
Sr. No. Description
(1)
Declare the interface as a target for input check by attaching a @Validated annotation.
(2)
Specify a constraint annotation of Bean Validation as an argument for the method.
  • [projectName]-domain/src/main/java/com/example/domain/model/Todo.java
package com.example.domain.model;

import java.io.Serializable;
import javax.validation.constraints.Null;

// (1)
public class Todo implements Serializable {

    private static final long serialVersionUID = -1L;

    // omitted

    @Null
    private String description;

    // omitted

    private boolean finished;

    // omitted

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isFinished() {
        return finished;
    }

    public void setFinished(boolean finished) {
        this.finished = finished;
    }

}
Sr. No. Description
(1)
Define input check of JavaBean in Bean Validation.
In this example, @Null annotation is set as an example.
For details, refer “Input Validation”.
  • [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
@Inject
TodoService todoService;

@JmsListener(destination = "jms/queue/MessageQueue")
public void receive(Todo todo) {
    try {
        todoService.updateTodo(todo); // (1)
    } catch (ConstraintViolationException e) { // (2)
        // omitted
    }
}
Sr. No. Description
(1)
Implement Service method which performs input check.
(2)
Capture ConstraintViolationException which occurs at the time of constraint violation.
Any process can be performed after capturing the exception.
For examples of sending messages to another Queue like using a Queue for storing logical error messages, refer Exception handling at the time of asynchronous reception.

8.2.2.3.6. Transaction control

A transaction control function is used when data consistency is required to be guaranteed.
DefaultMessageListenerContainerof Spring JMS used in asynchronous reception incorporates JMS transaction management system. The function can be switched with acknowledgeattribute of listener-container. Implementation example while using the same is shown below.

Note

When message returns to Queue, it is asynchronously received once again. Since the cause of error is not resolved, the operations of rollback and asynchronous reception are repeated. A threshold value for number of resends after rollback can be set depending on JMS provider and when resend count exceeds the threshold value, the message is stored in Dead Letter Queue.

  • [projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
<!-- (1) -->
<jms:listener-container
    factory-id="jmsListenerContainerFactory"
    destination-resolver="destinationResolver"
    concurrency="1"
    error-handler="jmsErrorHandler"
    acknowledge="transacted"/>
Sr. No. Attribute name Details
(1)
cache
Specify cache level to determine cache targets such as Connection, Session or MessageConsumer.
Default is auto.
As described earlier in Basic asynchronous reception, consumer is specified when connection is not pooled in the application server.
  acknowledge
Specify transactedin acknowledge mode to enable transaction. Default is auto.

Warning

Since Connection or Session caching in the application is likely to be prohibited depending on the application server, enabling and disabling of cache should be determined in accordance with the specifications of application server to be used.

Warning

When you want to use asynchronous reception and synchronous transmission/reception together, and manage it with a single transaction, connection-factoryattribute of jms:listener-containerand ConnectionFactoryinstance specified in connectionFactoryproperty of JmsTemplateshould be made identical. Accordingly, since Spring shares Sessionto be used for asynchronous reception and synchronous transmission/reception, it is considered as a single transaction. At that time, following options can be considered to enable caching in both jms:listener-containerand JmsTemplate.

  • Leave caching of JMS related resource to AP server products and use the object fetched via JNDI lookup for both asynchronous reception and synchronous transmission/reception.
  • If MOM product has cache function of connectionfactory, use it as it is for both asynchronous reception and synchronous transmission/reception.
  • Use org.springframework.jms.connection.CachingConnectionFactoryas it is for both asynchronous reception and synchronous transmission/reception.

In either of these cases, noneshould be specified in cacheof listener-container.


Note

A method which performs exception handling other than roll back process in case of specific exceptions

When transaction control is enabled, the message returns to Queue due to roll back if the exception occurred in input check is thrown without getting captured. Since listener method asynchronously receives the message again which has returned in Queue, the sequence asynchronous reception Error occurrence Rollback is repeated a number of times for JMS provider configuration. In case of an error for which the cause of the error is not resolved even after retry, the error handling is done so as not to throw an exception from listener method after capturing, to restrain futile processes mentioned above. For details, refer Exception handling at the time of asynchronous reception.

In the application wherein DB transaction control is required, a transaction control policy must be determined after reviewing the relation between JMS and DB transactions based on business requirements. Following methods can be considered for link transactions of JMS and DB by asynchronous reception.

  1. A method which use global transactions by JTA
  2. A method which use “Best Effort 1 Phase Commit”
  3. A method which individually specifies JMS and DB transactions

Among these, using “A method to specify JMS and DB transactions separately” should be considered for the reasons described below. As introduced in transaction management of synchronous transmission (Transaction control) as well, global transaction by JTA requires overheads owing to protocol characteristics. In order to resolve this, a transaction management method was introduced which uses “Best Effort 1 Phase Commit” for synchronous transmission, however, it is not recommended in asynchronous reception since it results in transactions with incorrect framework. Generally, JMS transaction boundary is positioned on the outer side than using DB transaction boundary from the viewpoint of recovery. DefaultMessageListenerContainerof Spring consists of its own transaction management system, hence when you attempt to achieve “Best Effort 1 Phase Commit” by using transaction-manager attribute of jms:listener-containerin the settings of JTA, DB transaction boundary is positioned outside JMS transaction boundary. As a result, DB transaction is likely to be rolled back even though the message received asynchronously is processed normally.

Warning

When transaction process results are not returned to JMS provider like for example when connection with JMS provider is lost after receiving the message

When transaction process results are not returned to JMS provider like for example when connection with JMS provider is lost after receiving the message, transaction handling depends on JMS provider. Therefore, design considering loss of received message, reprocessing of message due to rollback etc.. Particularly, when the loss of message cannot be permitted, it is necessary to provide a system to compensate for loss of message or to consider the use of global transaction etc..

In this guideline, global transaction is not used. It is recommended to leave JMS transactions to transaction management implemented internally by Spring JMS as described above and to manage DB transactions by transactionManagerdefined in default settings of blank project. Implementation example is shown below.

  • [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
package com.example.listener.todo;

import javax.inject.Inject;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import com.example.domain.service.todo.TodoService;
import com.example.domain.model.Todo;
@Component
public class TodoMessageListener {
    @Inject
    TodoService todoService;

    @JmsListener(destination = "TransactedQueue") // (1)
    public void receiveTransactedMessage(Todo todo) {

        todoService.update(todo);

    }
}
  • [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.domain.model.Todo;

@Transactional // (2)
@Service
public class TodoServiceImpl implements TodoService {

    @Override
    public void update(Todo todo) {
        // omitted
    }
}
Sr. No. Description
(1)
Define @JmsListener annotation and specify DefaultJmsListenerContainerFactory which enables transaction control of JMS.
Since @JmsListener annotation refers to Bean name jmsListenerContainerFactory as a default, containerFactory attribute is omitted.
(2)
Define transaction boundary of DB.
Since value is omitted, it refers to Bean name transactionManagerby default. Bean name of JmsTransactionManagerand ChainedTransactionManager are specified in synchronous transmission however since it is left to Spring in asynchronous reception, it refers to DB transaction manager.
For details of @Transactional annotation, refer Regarding transaction management of Domain Layer Implementation.

Note

Nesting sequence for transaction boundary depends on the business requirements and JMS provider is often used for linking with external systems. In such a case, JMS transaction boundary is kept outside DB transaction boundary and recovery becomes easier when inward DB transaction is completed earlier. When DB transaction is committed and JMS transaction is rolled back, the message returns to the Queue. Hence the same message is processed again. It should be designed so as to allow DB update process to be re-tried at the time of reexecution of business process.


Behaviour when an application is created in accordance with the configuration and implementation example above is explained.

  • When processing of listener method is successfully completed
DefaultMessageListenerContainerstarts and commits JMS transaction whereas DB transaction manager starts and commits DB transaction.
JMS/DB Transaction
Sr. No. Description
(1)
Start JMS transaction.
(2)
Start DB transaction.
(3)
Commit DB transaction and terminate DB transaction.
(4)
Commit listener method successfully.
(5)
Commit JMS transaction and terminate JMS transaction.
  • When an unexpected exception occurs in business logic

When exception occurs in the service, both JMS transaction and DB transaction are rolled back.

JMS/DB Transaction
Sr. No. Description
(1)
Start JMS transaction.
(2)
Start DB transaction.
(3)
An unexpected exception occurs in the business logic.
(4)
Roll back DB transaction and terminate DB transaction.
(5)
Roll back JMS transaction and terminate JMS transaction.
Since JMS transaction is rolled back, the message is returned to the Queue.
  • When connection with JMS provider is lost after receiving the message and only DB transaction is committed

When the process involving asynchronous reception is not managed by global transaction, DB transaction and JMS transaction will be committed separately, there is a possibility that inconsistency may occur in JMS and DB state. Specifically, it is applicable to following cases.

  • When discontinuation of JMS connection is not detected, DB update process is continued and committed
  • When an exception occurs after committing DB transaction and prior to committing JMS transaction

In such a case, same message is likely to get processed again once JMS transaction is rolled back and it may result in sending the message with same contents for multiple times. With this background, even when same message is received multiple times, data integrity must be ensured. As a countermeasure, a method can be used wherein the information for uniquely identifying a request is recorded, and included in JMSMessageID, or Propertyor Body. This means that when the message is received, it is compared with information recorded in the past, and the processing is divided according to process status. Note that, there are differences in the events that can be handled according to information to be used, as shown below.

  • When JMSMessageIDis to be recorded, it can be done only for handling duplicate messages when the message is rolled back.
  • When a part of Property or Bodyis to be recorded, it can handle duplicate messages during an abnormality when a message having same meaning with respect to operations is sent for multiple times, besides when the message is rolled back.
JMS/DB Transaction
Sr. No. Description
(1)
Start JMS transaction.
(2)
Start DB transaction.
(3)
Commit DB transaction and terminate DB transaction.
(4)
An unexpected error occurs like loss of connection with JMS provider before committing JMS transaction.
(5)
JMS transaction commit fails.
Hence, a system to ensure consistency must be provided for loss of messages etc.

Note

When it is necessary to stringently control multiple transactions like JMS and DB by avoiding events given above, use of global transaction is considered. For global transaction, refer various product manuals.


8.2.2.3.7. Exception handling at the time of asynchronous reception

While performing transaction control, exceptions must be handled considering the rollback process.
For details of transaction control, refer Transaction control.
Exception handling of JMS is classified in 2 patterns given below based on the objective.
Table-Exception handling patterns
Sr. No. Objective of handling An example of exception for handling Handling method Handling unit
(1)
When exceptions occurring in business layer are to be individually handled
Business exception like input check error
Listener method
(try-catch)
Listener method unit
(2)
When the exceptions thrown by listener method are to be handled uniformly
System exceptions like input output error etc
ErrorHandler
JMSListenerContainer unit
  • When exceptions occurred in the business layer are to be handled individually

    The exceptions occurring in the business layer like “message contents are invalid” are trapped (try-catch) by listener method and handled by listener method unit.
    While performing transaction control, since exceptions must be thrown in DefaultMessageListenerContainer for the cases which require rollback, the captured exception must be thrown again.
    Implementation example is shown below.
    • [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
    @Inject
    TodoService todoService;
    
    @JmsListener(destination = "jms/queue/TodoMessageQueue")
    public JmsResponse<Todo> receiveTodo(Todo todo) {
        try {
            todoService.insertTodo(todo);
        } catch (BusinessException e) {
            return JmsResponse.forQueue(todo, "jms/queue/ErrorMessageQueue"); // (1)
        }
        return null; // (2)
    }
    
    Sr. No. Description
    (1)
    An object can be sent to a Queue for storing a logical error message by using forQueue method of JmsResponse class.
    In this example, since BusinessException which outputs the log in AOP is captured, log output process is not described explicitly. However, exceptions must be handled so as not to eliminate the cause of exception.
    When the messages are to be processed after roll back, by performing transaction control, the exception thus captured must be thrown.
    (2)
    When the messages are not sent, set return value to null.
  • When the exceptions thrown by listener method are to be handled uniformly

    While commonly handling exceptions, implementation class of ErrorHandler defined in error-handler attribute of <jms:listener-container/> is used.
    Configuration method is shown below.
    • [projectName]-web/src/main/resources/META-INF/spring/applicationContext.xml
    <!-- (1) -->
    <jms:listener-container
        factory-id="jmsListenerContainerFactory"
        destination-resolver="destinationResolver"
        concurrency="1"
        error-handler="jmsErrorHandler"
        cache="consumer"
        acknowledge="transacted"/>
    
    <!-- (2) -->
    <bean id="jmsErrorHandler"
        class="com.example.domain.service.todo.JmsErrorHandler">
    </bean>
    
    Sr. No. Description
    (1)
    Define a Bean name of error handling class in error-handler attribute of <jms:listener-container/>.
    (2)
    Define a Bean for error handling class.

    Implementation method is as shown below.

    • [projectName]-web/src/main/java/com/example/listener/todo/JmsErrorHandler.java
    package com.example.listener.todo;
    
    import org.springframework.util.ErrorHandler;
    import org.terasoluna.gfw.common.exception.SystemException;
    
    public class JmsErrorHandler implements ErrorHandler {  // (1)
    
       @Override
        public void handleError(Throwable t) { // (2)
            // omitted
            if (t.getCause() instanceof SystemException) {  // (3)
    
                // omitted system error handling
    
            } else {
                // omitted error handling
            }
        }
    }
    
    Sr. No. Description
    (1)
    Create an error handling class which implements ErrorHandler interface.
    (2)
    Exception occurring in the listener method is wrapped in org.springframework.jms.listener.adapter.ListenerExecutionFailedException and passed as an argument.
    (3)
    Determine any exception class and implement error handling associated with the exception.
    t.getCause() must be executed to fetch the exception occurred in the application.

8.2.2.4. A method wherein the messages are synchronously received

Synchronous reception in JMS provider is achieved by using JmsMessagingTemplate.
Messages can be received within any timing by using synchronous reception.
Architecture should be determined after thoroughly examining the implementation method which does not use synchronous reception.
Configuration of Bean definition file for synchronous reception is shown below.
  • [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
<bean id="cachingConnectionFactory"
   class="org.springframework.jms.connection.CachingConnectionFactory"> <!-- (1) -->
   <property name="targetConnectionFactory" ref="connectionFactory" /> <!-- (2) -->
   <property name="sessionCacheSize" value="1" />  <!-- (3) -->
</bean>

<!-- (4) -->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
   <property name="connectionFactory" ref="cachingConnectionFactory" />
   <property name="destinationResolver" ref="destinationResolver" />
</bean>

<!-- (5) -->
<bean id="jmsMessagingTemplate" class="org.springframework.jms.core.JmsMessagingTemplate">
    <property name="jmsTemplate" ref="jmsTemplate"/>
</bean>
Sr. No. Description
(1)
Define a Bean for org.springframework.jms.connection.CachingConnectionFactory which performs caching of Session, MessageProducer and MessageConsumer.
Cache function can be used by using JMS provider specific ConnectionFactory which is looked up in Bean definition or JNDI name, by wrapping it in CachingConnectionFactory instead of using it as it is.
(2)
Specify ConnectionFactorywhich is wrapped up in Bean definition or JNDI name.
(3)
Set cache number of Session. (default value is 1)
Although 1 is specified in this example, cache number should be changed appropriately corresponding to performance requirement.
If a session is required to be continued even after exceeding this cache number, a new session is created and destroyed repeatedly without using a cache.
It is likely to cause reduction in process efficiency resulting in performance degradation.
(4)
Define a Bean for JmsTemplate.
JmsTemplate alternates for a low level API handling (JMS API calling).
For the attributes that can be configured, refer list of attributes of JmsTemplate shown below.
(5)
Define a Bean for JmsMessagingTemplate. Inject JmsTemplate which alternates as a synchronous reception process.
List of attributes of JmsTemplate related to synchronous reception are shown below.
Configuration must be done as required.
Sr. No. Configuration item Details Mandatory Default value
connectionFactory
Set ConnectionFactoryto be used.
Nil (since it is mandatory)
pubSubDomain
Set for messaging model.
Set falsefor PTP (Queue) model and truefor Pub/Sub (Topic).
- false
sessionTransacted
Set whether the transaction is controlled in the session.
In this guideline, since transaction control described later is used, default false is recommended.
- false
sessionAcknowledgeMode
Set confirmation response mode of session for sessionAcknowledgeMode.
For details, refer JavaDoc of JMSTemplate.

Todo

Details of sessionAcknowledgeMode will be added later.

-
1
receiveTimeout
Set timeout period (milliseconds) at the time of synchronous reception. If it is not set, wait till the message is received.
If the timeout period is not set, it impacts the subsequent processes, hence an appropriate timeout period must always be set.
-
0
messageConverter
Set message converter.
Default can be used in the range introduced in the guideline.
- SimpleMessageConverter(*1) is used.
destinationResolver
Set DestinationResolver.
This guideline recommends setting JndiDestinationResolver which performs name resolution by JNDI.
-
DynamicDestinationResolver(*2) is used.
(If DynamicDestinationResolver is used, name resolution of Destination is performed by JMS provider.)
defaultDestination
Specify existing Destination.
When the Destination is not specified explicitly, this Destination is used.
- null(no existing Destination)

(*1)org.springframework.jms.support.converter.SimpleMessageConverter

(*2)org.springframework.jms.support.destination.DynamicDestinationResolver

Messages are received synchronously by receiveAndConvert message of JmsMessagingTemplate class. Implementation example is shown below.

  • [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
package com.example.domain.service.todo;

import javax.inject.Inject;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import com.example.domain.model.Todo;

@Service
public class TodoServiceImpl implements TodoService {
    @Inject
    JmsMessagingTemplate jmsMessagingTemplate;

    @Override
    public String receiveTodo() {

       // omitted
       Todo retTodo = jmsMessagingTemplate.receiveAndConvert("jms/queue/TodoMessageQueue", Todo.class);   // (1)

    }
}
Sr. No. Description
(1)
Receive message from specified Destination using receiveAndConvert method of JmsMessagingTemplate.
receiveAndConvert method can fetch the class for which type conversion is done, by specifying the class for conversion, in the second argument.
A header item can be fetched by Message object of Spring Framework, by using receive method.

8.2.3. Appendix

8.2.3.1. JMS provider dependent configuration

Configuration varies for each JMS provider. Configuration for each JMS provider is explained below.

8.2.3.1.1. While using Apache ActiveMQ

Configuration while using Apache ActiveMQ is explained.

  • JMS provider specific configuration for application server

    JMS provider requires specific configuration.
    In Apache ActiveMQ, environment variable must be added to starting variable of application server to ensure that it consists of objects wherein payload of received messages is permissible.
    For details, refer ObjectMessage.
    An example of adding environment variable to startup argument of Apache Tomcat is shown below. Refer Configuring JBoss EAP to Run as a Service in case of JBoss Enterprise Application Platform 7.0, Service Configuration in case of JBoss Enterprise Application Platform 6.4 and Starting Managed Servers with a Startup Script in case of Weblogic.
    • $CATALINA_HOME/bin/setenv.sh
    # omitted
    # (1)
    -Dorg.apache.activemq.SERIALIZABLE_PACKAGES=java.lang,java.util,org.apache.activemq,org.fusesource.hawtbuf,com.thoughtworks.xstream.mapper,com.example.domain.model
    # omitted
    
    Sr. No. Description
    (1)
    Add package for an arbitrary object to be allowed. java.lang,java.util, org.apache.activemq, org.fusesource.hawtbuf and com.thoughtworks.xstream.mapper are the settings required while using Apache ActiveMQ.
    “com.example.domain.model” is added as a required configuration value in this sample.
  • Adding a library

    JMS API is not included in the spring-jms library.
    Although JMS API is normally included in the JMS provider library, JMS API is added to pom.xml if JMS API is not included in JMS provider library.
    activemq-client is added to pom.xml of domain project and web project as a library for build.
    Further, activemq-client and its dependent libraries are added to the application server.
    • [projectName]-domain/pom.xml
    • [projectName]-web/pom.xml
    <dependencies>
    
        <!-- (1) -->
        <dependency>
            <groupId>org.apache.activemq</groupId>
            <artifactId>activemq-client</artifactId>
            <scope>provided</scope>
        </dependency>
    
    </dependencies>
    
    Sr. No. Description
    (1)
    Add client library of Apache ActiveMQ to dependencies as a build. Since JMS API is also incorporated in activemq-client library, it is not necessary to add JMS API as a library.

Note

In the above setting example, since it is assumed that the dependent library version is managed by the parent project terasoluna-gfw-parent , specifying the version in pom.xml is not necessary. The above dependent library used by terasoluna-gfw-parent is defined by Spring IO Platform.


Warning

The version of the library used while establishing a connection with Apache ActiveMQ is defined in Spring IO Platform which is used by TERASOLUNA Server Framework for Java. Hence, care must be taken while determining Apache ActiveMQ version. Note that, library and middleware versions are likely to be inconsistent while upgrading the version of TERASOLUNA Server Framework for Java.


  • JNDI registration to application server

    For registration of JNDI to application server, refer Manually integrating Tomcat and ActiveMQ.
  • Configuration when JNDI is not used.

    Although this guideline recommends a method to resolve names using JNDI,
    JNDI is not used while connecting to JMS provider during the implementation of a simple test which is not run on the application server.
    In such a case, a Bean of implementation class of ConnectionFactory must be generated.
    Further, use of JNDI is also specified for Queue, however, if a Queue specified in the Destination by using a JMS provider function does not exist, a Queue of specified name can be dynamically generated.
    Internal Broker of Apache ActiveMQ must be used for establishing a connection without passing through the application server.
    For configuration of internal Broker for Apache ActiveMQ, refer How do I embed a Broker inside a Connection.
    Following configuration should be added to the context for the testing.
    • [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
    <!-- (1) -->
    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <constructor-arg value="tcp://localhost:61616"/>  <!-- (2) -->
    </bean>
    
    Sr. No. Description
    (1)
    Define a Bean for ConnectionFactory of Apache ActiveMQ.
    (2)
    Specify beginning URL of Apache ActiveMQ. Beginning URL sets the value for each environment.

Note

When the configuration method of ConnectionFactory is to be changed by JNDI and bean definition, based on development phase, configuration should be described in [projectName]-env/src/main/resources/META-INF/spring/[projectName]-env.xml.

8.2.3.2. Mass-mailing of identical messages

If JmsMessageTemplate is to be used for mass mailing of identical message, memory usage is likely to increase.
Hence, a method wherein implementation is done by using send method of JmsTemplate class must be considered.
Reason being, an instance of org.springframework.jms.core.MessageCreator class is generated while sending a message in JmsMessageTemplate.
In order to prevent the generation of unnecessary instance, the messages are sent by send method of JmsTemplate class wherein MessageCreator instance is not generated during transmission thus reducing the amount of memory used.
An example of code wherein a string is sent 100 times to an identical destination is shown below.
  • [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
 package com.example.domain.service.todo;

 import java.io.IOException;
 import javax.inject.Inject;
 import javax.jms.JMSException;
 import javax.jms.Message;
 import javax.jms.Session;
 import javax.jms.TextMessage;
 import org.springframework.jms.core.JmsTemplate;
 import org.springframework.jms.core.MessageCreator;
 import org.springframework.stereotype.Service;

 @Service
 public class TodoServiceImpl implements TodoService {

    @Inject
    JmsTemplate jmsTemplate; // (1)

    @Override
    public void sendManyMessage(final String messageStr) throws IOException {
        MessageCreator mc = new MessageCreator() { // (2)
            public Message createMessage(Session session) throws JMSException {
                TextMessage message = session.createTextMessage(); // (3)
                message.setText(messageStr);

                // omitted
                return message;
            }
        };
        for (int i = 0; i < 100; i++) {
            jmsTemplate.send("jms/queue/TodoMessageQueue", mc); // (4)
        }
    }
}
Sr. No. Description
(1)
When JmsMessagingTemplate is used, MessageCreater is generated at the time of sending. Hence, JmsTemplate which can define generation of MessageCreater by isolating it from sending, is used.
(2)
Generate instance of MessageCreator for creating Message of JMS.
(3)
When messages are sent by send method of JmsTemplate class, instances of MessageCreator are no longer generated for each loop and the amount of memory can be reduced.

8.2.3.3. Sending and receiving large size data

While handling data of large size like Image data (Data of size 1MB or more as a rough indication), OutOfMemoryError is likely to occur due to number of concurrent transactions and heap size. In standard API of JMS, only StreamMessage which sends primitive type data and ByteMessage which can send uninterpreted byte stream can handle large size data as a stream. Hence, a specific API offered for each JMS provider is used instead of JMS API.

8.2.3.3.1. While using Apache ActiveMQ

A message of large size can be received or sent by using Blob Message. Implementation example is shown below.

Note

Since Apache ActiveMQ specific API is used while using org.apache.activemq.BlobMessage, Message and CachingConnectionFactory offered by Spring Framework cannot be used. It is recommended to separately define JmsTemplate for BlobMessage while using BlobMessage considering the impact on the performance.

  • Configuration

    While sending a message by using BlobMessage, the message is stored in the server which is temporarily run by Apache ActiveMQ, instead of heap area. Definition example for storage destination for the messages is shown below.

    • [projectName]-domain/src/main/resources/META-INF/spring/[projectName]-infra.xml
    <bean id="connectionFactory"
       class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL">
          <!-- (1) -->
          <value>tcp://localhost:61616?jms.blobTransferPolicy.uploadUrl=/tmp</value>
        </property>
    </bean>
    
    Sr. No. Description
    (1)
    Define a directory of Apache ActiveMQ server which temporarily stores messages.
    http://localhost:8080/uploads/ is set as a default in jms.blobTransferPolicy.uploadUrl wherein location for temporary file can be specified by overloading default or brokerURL.
    For example, the file is temporarily stored in /tmp.
  • Sending

    An implementation example of sending class which use Blob Message is shown below.

    • [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
    package com.example.domain.service.todo;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import javax.inject.Inject;
    import javax.jms.JMSException;
    import javax.jms.Message;
    import javax.jms.Session;
    import org.apache.activemq.ActiveMQSession;
    import org.apache.activemq.BlobMessage;
    import org.springframework.jms.core.JmsTemplate;
    import org.springframework.jms.core.MessageCreator;
    import org.springframework.stereotype.Service;
    
    @Service
    public class TodoServiceImpl implements TodoService {
        @Inject
        JmsTemplate jmsTemplate;
    
        @Override
        public void sendBlobMessage(String inputFilePath) throws IOException {
    
            Path path = Paths.get(inputFilePath);
            try (final InputStream inputStream = Files.newInputStream(path)) {
    
                jmsTemplate.send("jms/queue/TodoMessageQueue", new MessageCreator() {
                    public Message createMessage(Session session) throws JMSException {
    
                        ActiveMQSession activeMQSession = (ActiveMQSession) session;  // (1)
    
                        BlobMessage blobMessage = activeMQSession.createBlobMessage(inputStream);  // (2)
                        return blobMessage;
                    }
                });
            }
        }
    }
    
    Sr. No. Description
    (1)
    Use org.apache.activemq.ActiveMQSession - a Apache ActiveMQ specific API in BlobMessage.
    (2)
    Generate BlobMessage from ActiveMQSessionby specifying sending data.
    Arguments of createBlobMessage method can be specified by File, InputStream and URL class.
  • Receiving

    An implementation example of receiving class is shown below.

    • [projectName]-web/src/main/java/com/example/listener/todo/TodoMessageListener.java
    package com.example.listener.todo;
    
    import java.io.IOException;
    import javax.inject.Inject;
    import javax.jms.JMSException;
    import org.apache.activemq.BlobMessage;
    import org.springframework.jms.annotation.JmsListener;
    import org.springframework.stereotype.Component;
    import com.example.domain.service.todo.TodoService;
    @Component
    public class TodoMessageListener {
        @Inject
        TodoService todoService;
        @JmsListener(destination = "jms/queue/TodoMessageQueue")
        public void receiveBlobMessage(BlobMessage message) throws IOException, JMSException {
         todoService.fileInputBlobMessage(message);
            // omitted
        }
    }
    
    • [projectName]-domain/src/main/java/com/example/domain/service/todo/TodoServiceImpl.java
    package com.example.domain.service.todo;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.file.Files;
    import java.nio.file.Path;
    import java.nio.file.Paths;
    import org.apache.activemq.BlobMessage;
    import org.springframework.stereotype.Service;
    
    @Service
    public class TodoServiceImpl implements TodoService {
    
        @Override
        public void fileInputBlobMessage(BlobMessage message) throws IOException {
            try(InputStream is =  message.getInputStream()){   // (1)
                Path path = Paths.get("outputFilePath");
                Files.copy(is, path);
                // omitted
            }
        }
    }
    
    Sr. No. Description
    (1)
    Fetch data from received BlobMessage as InputStream.