In our introduction to local transactions article, we looked at what transactions are, how they are defined in JMS and in IBM MQ, and shared a few samples for simple usage in stand-alone Java SE applications.
In this article, we look at what you need to know to write JMS messaging applications for enterprise application servers. Application servers provide abstractions that remove some of the complexity from your app so you can concentrate on the business logic alone. The trade-off is that you have to know how to use the common services like transactions that they provide to achieve your goal.
Jakarta Enterprise Edition (Jakarta EE)
Enterprise applications are not usually stand alone; there are lots of them, doing different things, and connecting to resources through different protocols to exchange data and keep the state of all the participating resources consistent.
It makes sense for these enterprise applications to have a common environment to run in. And, once they’re there, it might make sense that some of the repeated, common functionality is centralized on the platform so that new apps don’t have to implement the same behaviors over and over again.
For Java, we start with Jakarta Enterprise Edition (Jakarta EE). Jakarta EE is a specification that defines a Java platform that is referred to as a Jakarta EE container, which is a server environment in which our application can run that also provides common services and APIs.
The aim of the Jakarta EE specification is to standardize how distributed computing and web services work in enterprise servers. For a good overview of the Java EE Architecture, see this page from the book by Arun Gupta, Java EE 7 Essentials and this Stack Overflow answer that summarizes the Java EE and Jakarta EE specifications.
Basic application server concepts
Before we jump into how to build transactional JMS enterprise applications on an app server, let’s review some basic app server concepts first:
Enterprise Java beans (EJBs). Enterprise java beans are server-side components that provide some business logic to application clients. Clients invoke the EJB methods to use a service provided by the bean. EJBs run in EJB containers which provide services like transactionality and security.
Message-driven beans (MDBs). Message-driven beans are a type of EJBs. They allow enterprise applications to process messages asynchronously. They are usually message listeners listening for messages from applications or other components. When a message arrives, the EJB container calls the message listener’s
onMessagemethod to process the message.
Contexts and dependency injection. Contexts provide lifecycle management for stateful components like beans and dependency injection allows for injection of components into applications to be used at deployment time.
Descriptors and annotations. Deployment descriptors are
.xmlfiles that describe how applications should be deployed and what dependencies should be injected. Annotations are used in applications to add the injection in classes and methods, removing the need for XML configuration files. Annotations are more recent, but both are still in use and can be used in combination.
This is just the tip of the iceberg of what a developer should be familiar with in order to write good Jakarta EE applications that run in app servers.
Connecting Open Liberty and MQ via JMS
We used JMS in several tutorials to create stand-alone Java EE apps and have seen how transactions work with just JMS and IBM MQ, but what do transactional JMS enterprise applications look like when they are created to run on application servers?
Let’s look at a more specific set of resources that make up an app server, based on the Jakarta EE specification.
In the following diagram, there is the Open Liberty app server working with the MQ resource adapter to allow a message to be sent to a queue on an IBM MQ queue manager. Both Open Liberty and MQ support JMS messaging. A web application sends a message to a queue on which a message-driven bean is listening. The message-driven bean is also running in the app server but is not consuming any resources until a message arrives. The MQ resource adapter enables the communication between the Open Liberty app server and IBM MQ such that a message is sent to the queue and the listener gets the message when it arrives on the queue.
Figure 1 Applications using Liberty app server resources and MQ Resource adapter to connect to a queue manager and send/receive message
In the previous article, we saw that a in a Java SE scenario, the transaction was coordinated between IBM MQ as the messaging provider and the JMS API. We added a transacted property to a JMS context (or session) before starting a method or a group of methods and ended the transaction with a commit. Depending on how critical the messages we were sending inside the transaction were, we made sure that when rolling them back that they ended up on the queue they started from. If a message was just created before being a part of the transaction, it might have been thrown away. If the message was important and it didn’t come from a queue that it could go back to, we made use of the JMS API call to put it to a backout queue we named for this purpose. If we didn’t name the backout queue, we relied on the queue manager to put the message on the dead letter queue.
In the app server environment, service components take over the role of managing transactions. Rather than just knowing which JMS classes to use, you need to know that at a certain point the Jakarta EE platform will take over, do things under the covers, and provide transactionality for your app. Your application just needs to behave in the right way.
This scenario is where we get to the concept of transaction demarcation. This means setting the boundaries of where the transaction begins and ends.
In Jakarta EE, there are two ways to manage transactions. For each method, you need to understand what and how much you’re responsible for when including a transaction in your app.
- Container-managed transaction demarcation
In container-managed transaction demarcation, the EJB container sets the transaction boundaries. In basic terms, the transaction starts before the bean method starts and commits just before the method exits. Read more about container-managed transactions and how to use the attributes to set the scope for this kind of transaction.
- Bean-managed transaction demarcation
Bean managed transaction demarcation allows developers to explicitly mark the transaction boundaries. It allows for a more fine-grain control over transactions. Read this tutorial for more on bean managed transactions.
Using developer-friendly Spring
Spring promises a more developer friendly approach to using the resources that a Jakarta EE compliant framework provides.
Spring framework modules include these:
The following figure is Figure 2.1 from the Spring Framework docs
Spring provides its own abstraction on top of the Jakarta EE specification. Spring works with plain old java objects (POJOs) and does not require you to understand how Jakarta EE works. The Spring framework hides the app server layer and gives you a different way of achieving your goals.
With the EJB container, you can give it an annotation or a descriptor and the app server orchestrates things for you, with the EJ bean you do it programmatically. In Spring, it is the same. You can rely on the container and they’ll manage it for you, or you can do it yourself. You just have to learn a little about Spring annotations and classes, but the framework will do the rest for you.
If your application is built for Spring, it is still portable. Spring gives you an app server agnostic way of doing things.
Declarative transaction management is Spring’s preferred way of working with transactions because it has less impact on the application code. Spring warns developers that it is useful to understand that the functionality of transactions goes beyond the
@Transactional annotation and the
@EnableTransactionManagement configuration. The support for transactions is provided through Spring’s Aspect Oriented Programming (AOP) proxies and transactional metadata. Read more about it in this section of the Spring framework docs.
We’re mentioning Spring’s declarative transaction management because this is what we’re using in our IBM MQ samples. In practical terms, we can demonstrate simple usage through these samples. Let’s see how transactions work in Spring.
Transactions with IBM MQ and Spring
We provide two sets of simple transacted samples.
The simple application shows a transaction with a commit and a rollback.
The requester sample is a request sample that puts a message on the queue using the Spring send and receive method and provides a temporary queue where it waits for a reply.
The responder sample is a message-driven response/listener sample that receives a message, gets the reply queue from the requester, and sends a message back.
These samples are designed to work with the IBM MQ container with the default developer configuration.
How do I get the samples? We give you a couple of options for exploring the samples.
The samples are included in this GitHub repository that has the code for integrating MQ JMS with Spring (the
mq-spring-boot-starter). Follow the instructions in the main
mq-jms-springReadme to clone the repo, build it, and run the samples locally with Gradle.
If you need step-by-step instructions, you can also clone the
mq-dev-patternsrepository and follow the instructions in the
Spring directory Readmeto use the samples from
mq-jms-springand run them with Maven.
The features of Spring that the samples are using
For a quick note on some of the annotations we’re using in our samples, read a bit more below or jump straight into the samples.
We use Spring Boot in our applications. Spring Boot is an extension to Spring that allows for building and running of stand-alone Spring applications without much configuration. Along with the
mq-spring-boot-starter that provides the helper classes for integrating with IBM MQ, you can get started very quickly.
Externalized configuration properties
We use Spring’s
externalized configuration properties in the form of an
application.properties file which Spring Boot finds and loads when the application starts. This is how we provide some of the MQ connection variables and user details for our app to access the queue manager.
ibm.mq.queueManager=QM1 ibm.mq.channel=DEV.APP.SVRCONN ibm.mq.connName=localhost(1414) ibm.mq.tempModel=DEV.APP.MODEL.QUEUE ibm.mq.user=app ibm.mq.password=passw0rd
Messaging with JMS
As mentioned in the
Messaging with JMS Spring guide we use the @EnableJms annotation to start the discovery of methods annotated with
@JmsListener and create the message listener container in the background.
We use the JMSTemplate to create the JMS Template object to control connections and sessions.
We use the @EnableTransactionManagement annotation to make use of the declarative transaction management.
We use the JmsTransactionManager because we want to retrieve the JMS Session and use the same transaction to send a reply after we get a request message.
TransactionStatus represents the status of a transaction and can be retrieved to find out the status information and programmatically request a rollback.
In this article, we looked at how JMS messaging applications work in applications servers, both Jakarta EE and Spring. We looked at what it takes for transactions to work in such environments.
While Jakarta EE compliant application servers offer a sophisticated environment for applications to make use of the platform’s services such as transactionality, their complexity, packaging, and deployment strategies might create barriers for developers to get started with easily.
The Spring framework seeks to simplify access to its resources by cutting out the middle layer by hiding it behind the abstractions that POJOs can use through straight forward annotations while the framework wires everything together as needed for applications to work.
Transactions are an essential part of enterprise application programming. Regardless of whether your applications need to work with Jakarta EE servers or Spring, if the messaging payload is of value, IBM MQ is flexible and can support either.