Java EE Stateful Session Bean (EJB) example

31 July 2013
By Gonçalo Marques
In this tutorial we will see how to create a simple Stateful Session Bean (EJB) and use it in a web application context, more precisely a Java Servlet.

Introduction

Stateful Session Beans - as the name states (and opposite to Stateless Session Beans) - are able to keep state across multiple calls from a given client during an established session.

A Stateful EJB is usually coupled with a single client representing a conversation, where the EJB keeps the conversational state. Naturally many instances of a given Stateful EJB may exist simultaneously if we also have multiple simultaneous clients: Each EJB will be coupled to a single client.

The EJB container is responsible to manage the Statefull EJB lifecycle as we will see further in this tutorial. An usual real-life example of a Stateful EJB usage relies in the implementation of a web application's shopping cart and that's exactly what we will do in this example.

This tutorial considers the following environment:

  1. Ubuntu 12.04
  2. JDK 1.7.0.21
  3. Glassfish 4.0

A Product entity

If we are building a shopping cart we will need to have Products, so we will start by defining a simple class representing a product:

Product.java

package com.byteslounge.model;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "PRODUCT")
public class Product implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "ID_PRODUCT", nullable = false)
  private int id;

  @Column(name = "DESCRIPTION", nullable = false)
  private String description;

  public int getId() {
    return id;
  }

  public String getDescription() {
    return description;
  }

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

}

If you are familiar with JPA you have already noticed that this Product class is defined as a JPA entity. This entity is mapped against the following table:

CREATE TABLE PRODUCT (
ID_PRODUCT INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
DESCRIPTION VARCHAR (32) NOT NULL
);

We are keeping the Product class very simple as this is not the focus of the tutorial. Next step is to create the Stateful Session Bean.

The Stateful Session Bean

Now it's time to define the main component of this tutorial: The Stateful EJB that represents the shopping cart.

ShoppingCartBean.java

package com.byteslounge.ejb;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
import javax.ejb.Stateful;
import javax.ejb.StatefulTimeout;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import com.byteslounge.model.Product;

@Stateful
@StatefulTimeout(unit = TimeUnit.MINUTES, value = 30)
public class ShoppingCartBean implements ShoppingCart {

  @PersistenceContext
  private EntityManager em;
  
  private List<Product> products;
  
  @PostConstruct
  private void init(){
    products = new ArrayList<>();
  }
  
  @Override
  public void addProduct(Product product){
    products.add(product);
  }
  
  @Override
  @TransactionAttribute(TransactionAttributeType.REQUIRED)
  public void completePurchase(){
    for(Product product : products){
      em.persist(product);
    }
    products.clear();
  }

}

We define an EJB as stateful by annotating the bean class with @Stateful. We also defined the Stateful EJB timeout with @StatefulTimeout annotation. If the EJB is to be used in a web application context the timeout should be synchronized with the HTTP session timeout.

Basically we have a product list that will be used to store the products we will be adding to the shopping cart (method addProduct is used to add a product to the cart). Method init is used to initialize the product list. Note that this method is annotated with @PostConstruct so it will be called by the EJB container during bean initialization.

Finally we are injecting the Entity Manager that will be used to persist the products list when the order is finished (method completePurchase).

The Stateless EJB is implementing a simple Local interface:

ShoppingCart.java

package com.byteslounge.ejb;

import javax.ejb.Local;

import com.byteslounge.model.Product;

@Local
public interface ShoppingCart {

  void addProduct(Product product);
  
  void completePurchase();

}

A testing Servlet

We will now use a Servlet to handle simulated HTTP requests made to a web application that represent the action of adding products to the shopping cart (and eventually complete the purchase). The Servlet may be defined as the following:

ShoppingServlet.java

package com.byteslounge.servlet;

import java.io.IOException;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.byteslounge.ejb.ShoppingCart;
import com.byteslounge.model.Product;

@WebServlet(name = "shoppingServlet", urlPatterns = {"/shopping"})
public class ShoppingServlet extends HttpServlet {

  private static final long serialVersionUID = 1L;
  private static final String SHOPPING_CART_BEAN_SESION_KEY 
    = "shoppingCart";

  protected void doGet(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException {
    
    // Obtain the EJB from the HTTP session
    ShoppingCart shoppingCartBean = 
      (ShoppingCart) request.getSession()
        .getAttribute(SHOPPING_CART_BEAN_SESION_KEY);
    
    if(shoppingCartBean == null){
      // EJB is not present in the HTTP session
      // so let's fetch a new one from the container
      try {
        InitialContext ic = new InitialContext();
        shoppingCartBean = (ShoppingCart) 
         ic.lookup("java:global/com-byteslounge/com-bytesloungeejb/ShoppingCartBean");
        
        // put EJB in HTTP session for future servlet calls
        request.getSession().setAttribute(
          SHOPPING_CART_BEAN_SESION_KEY, 
          shoppingCartBean);
        
      } catch (NamingException e) {
        throw new ServletException(e);
      }
    }
    
    String productName = request.getParameter("product");
    if(productName != null && productName.length() > 0){
      // Product is present in the HTTP request.
      // Let's add it to the shopping cart
      Product product = new Product();
      product.setDescription(productName);
      shoppingCartBean.addProduct(product);
    }
    
    String persist = request.getParameter("complete");
    if(persist != null && persist.equalsIgnoreCase("yes")){
      // Request instructs to complete the purchase
      shoppingCartBean.completePurchase();
    }
    
  }
  
}


The comments present in the Servlet are almost self-explanatory. When a request is made against the Servlet we check if we already have a shopping cart EJB instance stored in the HTTP session. If it's not yet present we fetch a new EJB instance from the EJB container and store it in the user's HTTP session for future usage.

Now we may issue requests against the servlet using the parameter product=[PRODUCT_NAME] to add products to the shopping cart and the parameter complete=yes when we decide to complete the order.

The local EJB will be available under the Portable Global JNDI name of:

java:global/com-byteslounge/com-bytesloungeejb/ShoppingCartBean

You should be able to find the global JNDI name in your EJB container's startup log.

Important: As you probably already know a single Servlet instance is used to handle multiple requests from multiple clients so the Stateful EJB should not be injected directly in the Servlet and kept as an instance property, or we will face obvious thread-safety related issues. In our case we are fetching it from JNDI inside doGet method and storing it in the HTTP session so each user will have it's own Sateful EJB instance.

Testing

After deploying our application we may start issuing requests to add products to the shopping cart:

http://localhost:8080/byteslounge/shopping?product=dvd

http://localhost:8080/byteslounge/shopping?product=book

After adding a dvd and a book to the shopping cart we complete the purchase:

http://localhost:8080/byteslounge/shopping?complete=yes

Now let's list the PRODUCT table contents:

PRODUCT table content
Product table content

As we can see both of the products were successfully persisted into the database.

If we use more than a single web browser and keep adding products to the cart simultaneously - and eventually finalizing the orders - we will observe that each client will have it's own Stateful EJB instance and consequently it's own shopping cart view.

Downloadable sample

The downloadable sample available at the end of this page is a complete Maven project where everything is already configured and ready to be deployed in an Enterprise Container. We used Glassfish in our case.

If you are using the sample as-is don't forget to configure the datasource in your container and/or modifying the supplied persistence unit configuration (persistence.xml) if needed.

Download source code from this article

Related Articles

Comments

About the author
Gonçalo Marques is a Software Engineer with several years of experience in software development and architecture definition. During this period his main focus was delivering software solutions in banking, telecommunications and governmental areas. He created the Bytes Lounge website with one ultimate goal: share his knowledge with the software development community. His main area of expertise is Java and open source.

GitHub profile: https://github.com/gonmarques

He is also the author of the WiFi File Browser Android application: