Java EE Stateful Session Bean (EJB) example
In this article we are going to see how you can use Stateful Session Beans to keep track of state across a client Session in a simple Web Application.
1. Introduction
Stateful Session Beans usually hold information about a specific client’s session, and holds that information throughout the whole session (opposed to Stateless Session Beans). A Stateful EJB instance is coupled with only one client. One client can of course have many EJB instances is his session.
In this example we are going to create a simple Shopping Cart application. The session bean will hold a list of products. As the client adds more products to his cart, this list will grow accordingly. Finally the client will be able to checkout his order and the products on the aforementioned list will be persisted in a MySQL database.
To implement the above functionality, we are going to create an EAR Project and an EJB Project that will host our Session Bean and a Dynamic Web Application that will host a Servlet, testing the aforementioned behavior. We are going to use Eclipse Java EE IDE 4,3 Kepler and Glassfish 4.0 as our container. Additionally we are going to use standard JPA 2.o to persist our products in a MySQL 5.6.14 database running on localhost. Here is a guide on how to Integrate MySQL with Glassfish.
2. Create a new Enterprise Application Project
Create a new Enterprise Application Project named StatefulEJBEAR
. In Eclipse IDE select File -> New -> Enterprise Application Project, fill in the form and click Finish:
3. Create a new EJB Projet
Create a new EJB Project called StatefulSessionBeansEJB
. We are going to create our session bean on this. Go to File -> New -> EJB Project and fill out the form. Be careful to select “Add EAR Project” and Select “StatefulEJBEAR
” as EAR project name:
4. Create a Stateful Session Bean
Open StatefulSessionBeansEJB
Project in the Project Explorer and in the folder ejbModule
create a new source package named com.javacodegeeks.enterprise.ejb
. In that package create a new Interface
that will be a local view of the EJB:
Cart.java:
package com.javacodegeeks.enterprise.ejb; import javax.ejb.Local; import com.javacodegeeks.enterprise.product.Product; @Local public interface Cart { void addProductToCart(Product product); void checkOut(); }
As you can see, we declare two methods, one to add a product to the cart and another to checkout the order.
And here is the Session Bean:
CartBean.java:
package com.javacodegeeks.enterprise.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.javacodegeeks.enterprise.product.Product; @Stateful @StatefulTimeout(unit = TimeUnit.MINUTES, value = 20) public class CartBean implements Cart { @PersistenceContext(unitName = "pu", type = PersistenceContextType.EXTENDED) private EntityManager entityManager; private List products; @PostConstruct private void initializeBean(){ products = new ArrayList<>(); } @Override public void addProductToCart(Product product) { products.add(product); } @Override @TransactionAttribute(TransactionAttributeType.REQUIRED) public void checkOut() { for(Product product : products){ entityManager.persist(product); } products.clear(); } }
As you can see, our Session bean implements our Cart
interface and simply holds a List of products (more on class Product
later).
In the above code notice:
- We use
@Stateful
to annotate the class as a Stateful Session Bean. - We declare a timeout with
@StatefulTimeout(unit = TimeUnit.MINUTES, value = 20)
annotation. This timeout denotes the amount of time that the bean should exist, and thus be valid for the session. It should correspond with the HTTP Session timeout value. - We use @PersistenceContext to inject an EntityManager that will handle the persistence of our products.
- We use
@PostConstruct
annotation onprivate void initializeBean()
method. This will denote to the EJB container to execute that method on bean initialization. You can view it as a constructor. - We use
@TransactionAttribute(TransactionAttributeType.REQUIRED)
annotation onpublic void checkOut()
method. This annotation is required to denote that the container is about to invoke a business method within a transaction context. As you can, see in that method the products on the list are persisted in the database.
5. The Product Entity class
This is the object representing a simple product in our cart application. It consists of an id and a type. As we said, when the order is checked out we want the product on the cart to be persisted in a database. We used JPA 2.0 annotations to map Product
class to a MySQL table. For this example, I’ve created a simple database named shop
and table named product
created with the script:
MySQL product table creation script:
CREATE TABLE `product` ( `ID` int(11) NOT NULL AUTO_INCREMENT, `TYPE` varchar(256) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY (`ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
So this is the table:
Let’s see the code of Product
class:
Product.java:
package com.javacodegeeks.enterprise.product; 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", catalog = "shop") public class Product implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "ID", nullable = false) private int id; @Column(name = "TYPE", nullable = false) private String type; public int getId() { return id; } public String getType() { return type; } public void setType(String description) { this.type = description; } }
The above annotations are self explanatory. Very briefly, we use:
- @Entity to declare the class as an Entity.
@Table(name = "PRODUCT", catalog = "shop")
to show that the class will be mapped to a table namedproduct
in a database namedshop
.@Id
,@GeneratedValue(strategy = GenerationType.IDENTITY)
to declare that the fieldid
of the classProduct
will be the primary key of the corresponding database table.@Column
to map a class field to a database column of tableproduct
.
Finally, for the persistence to work we need to create a persistence.xml
file inside ejbModule/META-INF
folder. The file looks like this:
application.xml:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0"> <persistence-unit name="pu" transaction-type="JTA"> <jta-data-source>jdbc/MySQLDataSource</jta-data-source> <class>com.javacodegeeks.enterprise.product.Product</class> </persistence-unit> </persistence>
Make sure you’ve integrated MySQL with Glassfish correctly using this quide. For more information on persistence.xml
file you can see this Oracle guide.
So the final project structure of StatefulBeansEJB
is :
6. Create a new Dynamic Web Project
Go to File -> New -> Dynamic Web Project. Fill out the form and make sure you check “Add project to an EAR” and put StatefulEJBEAR as the “EAR project name”:
After clicking “Finish”, go to the project Explorer and Right click on the Project StatefulSessionBeansTest
and go to Properties-> Deployment Assembly -> Add -> Porject -> StatefulEJB :
7. Create a new Servlet
Go to StatefulSessionBeansTest
Web project and create a new Servlet named ShoppingCartServlet
:
So this would be the final structure of the Web Project :
Here is the code of the Servlet:
ShoppingCartServlet.java:
package com.javacodegeeks.enterprise.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.javacodegeeks.enterprise.ejb.Cart; import com.javacodegeeks.enterprise.product.Product; @WebServlet("/ShoppingCartServlet") public class ShoppingCartServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final String CART_SESSION_KEY = "shoppingCart"; public ShoppingCartServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("Hello from servlet"); Cart cartBean = (Cart) request.getSession().getAttribute(CART_SESSION_KEY); if(cartBean == null){ // EJB is not yet in the HTTP session // This means that the client just sent his first request // We obtain a CartBean instance and add it to the session object. try { InitialContext ic = new InitialContext(); cartBean = (Cart) ic.lookup("java:global/StatefulEJBEAR/StatefulSessionBeansEJB/CartBean!" + "com.javacodegeeks.enterprise.ejb.Cart"); request.getSession().setAttribute(CART_SESSION_KEY, cartBean); System.out.println("shoppingCart created"); } catch (NamingException e) { throw new ServletException(e); } } String productName = request.getParameter("product"); if(productName != null && productName.length() > 0){ Product product = new Product(); product.setType(productName); cartBean.addProductToCart(product); System.out.println("product "+productName+" added"); } String checkout = request.getParameter("checkout"); if(checkout != null && checkout.equalsIgnoreCase("yes")){ // Request instructs to complete the purchase cartBean.checkOut(); System.out.println("Shopping cart checked out "); } } }
In the above Serlvet, when the user sents a GET request for the fist time, a new CartBean
instance will be obtained from the container and added to the session. Then the product
query parameter is parsed, and if it’s not null a new Product
with type
productName
, and will e added to the list of products in the session bean.
Then the checkout
query parameter is parsed and if it’s evaluated to 'yes'
the products in the session bean will be persisted.
Tip: If you are having trouble figuring out the Portable JNDI names for EJB PassivationObject look at the logs or output of Glassfish when deploying the project and you will find a line like this :2013-12-13T18:22:28.598+0200|INFO: EJB5181:Portable JNDI names for EJB PassivationObject: (java:global/StatefulBeans/StatefulEJB/PassivationObject, java:global/StatefulBeans/StatefulEJB/PassivationObject!com.javacodegeeks.enterprise.ejb.Passivation)
8. Test
Now we are simply going to deploy the Dynamic Web Application to Glassfish, and add some products on the cart. Then we will request to checkout the order.
Let’s say we want to add some products, we can issue the following requests :
http://localhost:8080/StatefulSessionBeansTest/ShoppingCartServlet?product=ram http://localhost:8080/StatefulSessionBeansTest/ShoppingCartServlet?product=mouse http://localhost:8080/StatefulSessionBeansTest/ShoppingCartServlet?product=ssd
While posting these requests this is the output from the console:
2014-01-07T22:02:07.622+0200|INFO: Hello from servlet
2014-01-07T22:02:07.684+0200|INFO: shoppingCart created
2014-01-07T22:02:07.687+0200|INFO: product ram added
2014-01-07T22:02:12.236+0200|INFO: Hello from servlet
2014-01-07T22:02:12.237+0200|INFO: product mouse added
2014-01-07T22:02:24.851+0200|INFO: Hello from servlet
2014-01-07T22:02:24.851+0200|INFO: product ssd added
Now to checkout the order you can issue:
http://localhost:8080/StatefulSessionBeansTest/ShoppingCartServlet?checkout=yes
This is the output from the console:
2014-01-07T22:19:46.444+0200|INFO: Hello from servlet
2014-01-07T22:19:46.537+0200|INFO: Shopping cart checked out
Here you can see the products on the database :
Dowload Eclipse Project
This was an example on Java EE Stateful Session Bean (EJB). Here are the Eclipse Projects of this example : StatefulEJBS.zip
I think that you should not create resource in get request:
Product product = new Product();
product.setDescription(productName);
shoppingCartBean.addProduct(product);
Hello Tomasz,
I agree with you when you say that HTTP GET requests should never change data in the server, but the tutorial is written this way to simplify both the user reading plus the tutorial testing section, ie. focusing on the Stateful Session Bean itself.
Thank you,
Gonçalo
Hi, I tried to pass this example to WAS Liberty but I have a trouble.
The lookup is not working, please maybe I am doing some wrong. please can you help me??
Application Error
SRVE0777E: Exception thrown by application class ‘com.javacodegeeks.enterprise.servlet.ShoppingCartServlet.doGet:51’
javax.servlet.ServletException: javax.naming.NameNotFoundException: javax.naming.NameNotFoundException: java:global/StatefulEJBEAR/StatefulSessionBeansEJB/CartBean!com.javacodegeeks.enterprise.ejb.Cart
at com.javacodegeeks.enterprise.servlet.ShoppingCartServlet.doGet(ShoppingCartServlet.java:51)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:575)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:668)
at com.ibm.ws.webcontainer.servlet.ServletWrapper.service(ServletWrapper.java:1274)
at [internal classes]
Caused by: javax.naming.NameNotFoundException: javax.naming.NameNotFoundException: java:global/StatefulEJBEAR/StatefulSessionBeansEJB/CartBean!com.javacodegeeks.enterprise.ejb.Cart
at com.ibm.ws.jndi.url.contexts.javacolon.internal.JavaURLContext.lookup(JavaURLContext.java:327)
at [internal classes]
at javax.naming.InitialContext.lookup(InitialContext.java:411)
at com.javacodegeeks.enterprise.servlet.ShoppingCartServlet.doGet(ShoppingCartServlet.java:44)
… 4 more
I also get the same error. Did you find a way to get pass by this?
I figure it out. If you don’t create database, don’t create persistence.xml file and comment out
// @PersistenceContext(unitName = “pu”, type = PersistenceContextType.EXTENDED)
// private EntityManager entityManager;
How u got servers folder there at first..What is to be included inside that folder?
I followed the same procedure but i dont find web.xml file in StatefullSessionBeansTest/WEB-INF/lib.. Plz tell me how to create web.xml file
Nice article! You may want to fix some spelling errors. More specifically the title “4. Create a Sateful Session Bean” must change to “4. Create a Stateful Session Bean” but also the sentence that follows the above title “Create a new Enterprise Application Project named SatefulEJBEAR” must be changed to “Create a new Enterprise Application Project named StatefulEJBEAR”.
Thank you. We fixed the spelling errors.
Thanks for the tutorial! I think I’m missing the point of using stateful objects here. I mean, since we save our cart object in our session, couldn’t we just use any stateless object and we can retrieve it again from the session with its previous state? I know that a session object is bound to only one user so we can attach whatever object to it and it will be unique to the session owner. Am I right?