Introduction to EJBs

In this tutorial, we learn how to use Enterprise JavaBeans. We use GlassFish, NetBeans, Derby, and Maven.

Enterprise JavaBean (EJB) is a server-side component that encapsulates the business logic of an application. EJBs are run in an EJB container, which is responsible for various system-level services, including transaction management, security, and concurrency control. EJBs are part of the Java EE specification.

GlassFish is the reference implementation of Java EE and it includes Enterprise JavaBeans container. We will run our examples in GlassFish. Apache Derby is an open source relational database implemented entirely in Java. Oracle distributes the same binaries under the name Java DB.

First EJB

We create a new web application in NetBeans. The project will be called MyFirstEJB. From the Server and Settings page, we select GlassFish server and change the context to myfirstejb.

Server and Settings
Figure: Server and Settings

In this dialog we select the application server, Java EE version, and context path.

index.html
<!DOCTYPE html>
<html>
    <head>
        <title>Test page</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <p>Test page</p>
    </body>
</html>

This is our index.html page. It will be returned if we access the root page of the application.

We right-click on the application icon and select a new EJB of Session Bean type. We call the bean MyFirstBean, type in com.zetcode.ejb package, and choose a stateless session type.

Creating a new session bean in NetBeans
Figure: Creating a new session bean in NetBeans

Stateless session bean does not maintain a conversational state with the client. When a client invokes the methods of a stateless bean, the bean's instance variables may contain a state specific to that client but only for the duration of the invocation. When the method is finished, the client-specific state is lost.

FirstBean.java
package com.zetcode.ejb;

import javax.ejb.Stateless;

@Stateless
public class FirstBean {
    
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
    public String sayHello() {
        
        StringBuilder sb = new StringBuilder("Hello ");
        sb.append(this.getName()).append("!");
        return sb.toString();
    }
}

MyFirstBean is a stateless session bean. A stateless session bean is created with the @Stateless decoration. It has a no-interface view, where local business interfaces are not used and all public methods of the bean class are automatically exposed to the caller.

public String sayHello() {
    
    StringBuilder sb = new StringBuilder("Hello ");
    sb.append(this.getName()).append("!");
    return sb.toString();
}

The MyFirstBean's job is to construct a greeting to the caller.

Next, we create a new servlet by right-clicking on the project icon and selecting a Servlet file type from the Web category. We call the servlet Greet and type in the com.zetcode.web package. We change the URL pattern to /greet.

New servlet
Figure: New servlet creation

In the new servlet dialog, we provide the servlet name and its package.

Greet.java
package com.zetcode.web;

import com.zetcode.ejb.FirstBean;
import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet(name = "Greet", urlPatterns = {"/greet"})
public class Greet extends HttpServlet {

    @EJB
    private FirstBean firstBean;
    
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        response.setContentType("text/plain;charset=UTF-8");
        
        firstBean.setName(request.getParameter("name"));
        String msg = firstBean.createMessage();
        
        try (PrintWriter out = response.getWriter()) {

            out.println(msg);

        }
    }
}

The Greet servlet reads a name parameter from the URL sent by the client, calls EJB's createMessage() business method and returns a response in plain text.

@EJB
private FirstBean firstBean;

The @EJB annotation injects the EJB into the servelt.

response.setContentType("text/plain;charset=UTF-8");

The servelt response is in plain text in UTF-8 charset.

firstBean.setName(request.getParameter("name"));

We retrieve the name parameter from the request and set it to the EJB.

String msg = firstBean.createMessage();

We call the createMessage() business method.

MyFirstEJB project structure
Figure: MyFirstEJB project structure

We build the application and deploy it to the GlassFish server. To build the application, right-click on the project icon and select Build; to deploy the application, right-click on the project icon and select Deploy.

$ curl localhost:8080/myfirstejb/greet?name=Jan
Hello Jan!

With the curl tool, we connect to the Greet servlet of the myfirstejb web application and pass it a parameter. The web application responds with a greeting.

$ curl localhost:8080/myfirstejb/
<!DOCTYPE html>
<html>
    <head>
        <title>Test page</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <p>Test page</p>
    </body>
</html>

Accessing the root page, the application returns the HTML test page.

Persisting data with Entity beans

In the second example, we create a web application that will read and save cars. The car objects will be saved in the Derby database.

We create a new Java web application with a car-app name. Then we create a new Car entity. Entity class file type is located in the persistence category. The package will be com.zetcode.persistence. The primary key type is Long and the create persistence unit option is checked.

In the next page, we change the persistence unit name to carpu, and select the default EclipseLink persistence provider. We select the jdbc/sample data source, and have the Create table generation strategy selected. The jdbc/sample data source refers to the sample database which is located in .netbeans-derby subdirectory of user's home directory by default.

Persistence provider
Figure: Persistence provider

In this view we provide the persistence unit name, persistence provider, data source, and table generation strategy.

Entity bean

Entity Bean is a type of Enterprise JavaBean that represents a business entity object existing in a persistent storage. An entity bean is identified by a primary key. Unlike session beans, which live during the lifetime of a client session, entity beans survive even EJB container crashes.

Car.java
package com.zetcode.persistence;

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="Cars")
public class Car implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="Id")
    private Long id;    
    
    @Column(name="Name")
    private String name;
    
    @Column(name="Price")
    private int price;      

    public Car() { }
    
    public Car(String name, int price) {
        
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }    

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

Car is an EJB entity bean. It is a business object to be stored in the Derby database.

@Entity
@Table(name="Cars")
public class Car implements Serializable {

Entity beans are decorated with the @Entity annotation. The entity maps to the Cars table.

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="Id")
private Long id;   

Each entity has a unique object identifier. This unique identifier, or primary key, enables clients to locate a particular entity instance. The @Id declares the identifier property of this entity bean, the @GeneratedValue annotation is used to specify how the primary key is generated, and the @Column maps the identifier to the Id column of the database table.

public Car() { }

A no-argument constructor is required by persistence frameworks.

EJB

We create a local, stateless ManageCarBean enterprise bean. This time the EJB is having a local interface view.

ManageCarBeanLocal.java
package com.zetcode.ejb;

import javax.ejb.Local;
import com.zetcode.persistence.Car;

@Local
public interface ManageCarBeanLocal {

    void saveCar(Car car);
    void setPrice(int price);
    void setName(String name);

    Car getCar(Long id);
}

The interface defines the methods to be used by the clients of the EJB. A client can access a session bean only through the methods defined in the bean's business interface.

@Local
public interface ManageCarBeanLocal {

The @Local decoration designates that the interface is a local business interface.

ManageCarBean.java
package com.zetcode.ejb;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.zetcode.persistence.Car;

@Stateless
public class ManageCarBean implements ManageCarBeanLocal {

    private String name;
    private int price;
    
    @PersistenceContext(unitName = "carpu")
    private EntityManager em;
    
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    @Override
    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public void saveCar(Car car) {
        
        em.persist(car);
    }

    @Override
    public Car getCar(Long id) {
        
        Car car = em.find(Car.class, id);
        return car;
    }
}

ManageCarBean reads and saves car objects.

@PersistenceContext(unitName = "carpu")
private EntityManager em;

The EntityManager is created by the container using the information in the persistence.xml. The @PersistenceContext annotation injects the entity manager into the bean. The manager is mapped to the carpu persistence unit. Entity manager is used to interact with the data via the persistence context.

@Override
public void saveCar(Car car) {
    
    em.persist(car);
}

The saveCar() method saves a car object into the database; in our case it is Derby.

@Override
public Car getCar(Long id) {
    
    Car car = em.find(Car.class, id);
    return car;
}

The getCar() method finds a car by its ID.

persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">

  <persistence-unit name="carpu" transaction-type="JTA">
    <jta-data-source>jdbc/sample</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables" />
    </properties>
  </persistence-unit>
  
</persistence>

The persistence.xml file is a configuration file that contains information about the database that we use in our application. The jdbc/sample is a built-in datasource shipped with the GlassFish server. In our application, we save our data in the Derby's pre-created sample database. The eclipselink.ddl-generation property automatically causes the Cars table to be created if it does not exist.

NetBeans Derby tool
Figure: NetBeans Derby tool

We can use NetBeans Derby tool to connect and manage our data in the database. The tool is located in the Services window.

Servlets

We create two servlets: SaveCar and ReadCar. We place them into the com.zetcode.web package. Servlets are created from the NetBeans Web category. Both servlets respond in plain text.

Apache Common Lang JARs
Figure: Apache Common Lang JARs

We also include Apache Common Lang JARs for helper methods. The library can be downloaded from the project website.

ValidateParameter.java
package com.zetcode.util;

import org.apache.commons.lang3.math.NumberUtils;

public class ValidateParameter {

    private static final int MAX_PRICE_CAR = 10_000_000;

    public static boolean validateName(String param) {

        return !(null == param || "".equals(param) || 
                NumberUtils.isNumber(param));
    }
    
    public static boolean validateId(String param) {

        return !(null == param || "".equals(param) || 
                !NumberUtils.isNumber(param));
    }   
    
    public static boolean validatePrice(String param) {

       if (null == param || "".equals(param) || !NumberUtils.isNumber(param)) {
           return false;
       }
       
       int price = Integer.valueOf(param);
       
       return !(price < 0 || price > MAX_PRICE_CAR);
       
    }     
}

A ValidateParameter class is used to validate the request parameters. Parameters may not be null or empty, and ID and price values must be numbers. In addition, the price must be within a reasonable range. We use the Apache Common Lang's NumberUtils.isNumber() to check for a numeric value.

SaveCar.java
package com.zetcode.web;

import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
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.zetcode.ejb.ManageCarBeanLocal;
import com.zetcode.persistence.Car;
import com.zetcode.util.ValidateParameter;

@WebServlet(name = "SaveCar", urlPatterns = {"/save"})
public class SaveCar extends HttpServlet {

    @EJB
    private ManageCarBeanLocal manageCarBean;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String sname = request.getParameter("name");
        String sprice = request.getParameter("price");

        String message;

        if (ValidateParameter.validateName(sname)
                && ValidateParameter.validatePrice(sprice)) {
            
            message = String.format("%s car is saved", sname);

            int price = Integer.valueOf(sprice);
            Car car = new Car(sname, price);
            manageCarBean.saveCar(car);

        } else {

            message = "Wrong parameters";
        }

        response.setContentType("text/plain;charset=UTF-8");

        try (PrintWriter out = response.getWriter()) {

            out.println(message);

        }
    }
}

The purpose of the SaveCar servlet is to call the appropriate EJB to save the car. The data for the car object are read from the URL sent by the client.

@WebServlet(name = "SaveCar", urlPatterns = {"/save"})

The @WebServlet annotation maps the SaveCar servlet to the /save path.

@EJB
private ManageCarBeanLocal manageCarBean;

A ManageCarBeanLocal enterprise bean is injected into the servlet.

String sname = request.getParameter("name");
String sprice = request.getParameter("price");

The parameters are read from the request.

if (ValidateParameter.validateName(sname)
        && ValidateParameter.validatePrice(sprice)) {

The parameters are being validated.

Car car = new Car(sname, price);
manageCarBean.saveCar(car);

A new car object is created and saved to the database. To save the car object, we utilize the ManageCarBean's saveCar() method.

ReadCar.java
package com.zetcode.web;

import com.zetcode.ejb.ManageCarBeanLocal;
import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
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.zetcode.persistence.Car;
import com.zetcode.util.ValidateParameter;

@WebServlet(name = "ReadCar", urlPatterns = {"/read"})
public class ReadCar extends HttpServlet {

    @EJB
    private ManageCarBeanLocal manageCarBean;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
            
        response.setContentType("text/plain;charset=UTF-8");

        String message;
        String pid = request.getParameter("id");

        if (ValidateParameter.validateId(pid)) {

            Long carId = Long.valueOf(pid);

            Car rcar = manageCarBean.getCar(carId);

            if (null != rcar) {

                message = String.format("Name: %s, Price: %d",
                        rcar.getName(), rcar.getPrice());

            } else {

                message = "Cannot find car with this ID";
            }
        } else {
            
            message = "Wrong parameters";
        }

        try (PrintWriter out = response.getWriter()) {

            out.println(message);
        }
    }
}

The ReadCar servlet calls the ManageCarBean enterprise bean to read data from the database; it selects a car object based on the provided ID.

String sid = request.getParameter("id");

An ID is read from the URL.

Long carId = Long.valueOf(pid);

Car rcar = manageCarBean.getCar(carId);

if (null != rcar) {

    message = String.format("Name: %s, Price: %d",
            rcar.getName(), rcar.getPrice());

} else {

    message = "Cannot find car with this ID";
}

The ManageCarBean's readCar() method is used to retrieve the car object.

web.xml
<?xml version="1.0" encoding="UTF-8"?>

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
         
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
    
    <error-page>
        <error-code>404</error-code>
        <location>/error404.txt</location>
    </error-page>
    
    <error-page>
        <location>/error.txt</location>
    </error-page>    

</web-app>

In the web.xml file, we provide two error pages. The error.txt is a default error page for all errors and the error404.txt is for 404 errors, which are triggered when the client requests a non-existing resource. Note that error404.txt must go before error.txt.

Error pages
Figure: Error pages

We place the two text files inside web pages.

error.txt
Error has occurred, check the GlassFish log file

This is the generic error page.

error404.txt
404 : Page was not found

This is the error page for 404 errors.

Deployment

It's time to deploy the application. The application is deployed by right-clicking on the web project and selecting the Deploy command. Note that when we clean the project, the application is undeployed. The deploy command also starts the selected application server if it is not already running.

$ ./asadmin list-applications
MyFirstEJB  <ejb, web>
car-app     <ejb, web>  
Command list-applications executed successfully.

The GlassFish's asadmin tool can be used to determine the currently deployed applications.

NetBeans automatically starts the Derby server when GlassFish starts. This can be turned off in the GlassFish server settings.

GlassFish server properties
Figure: GlassFish server properties

In the NetBeans' Services tab, we expand the Servers node and select the GlassFish properties. There we can see the Start Registered Derby Server option.

$ curl localhost:8080/car-app/doread
404 : Page was not found

If we try to reach a non-existing resource, we get the custom 404 error message.

$ curl "localhost:8080/car-app/save?name=Volvo&price=29000"
Volvo car is saved

A Volvo car is saved into the database. Note the usage of double quotes for the URL.

$ curl "localhost:8080/car-app/save?name=Bentley&price=299000000"
Wrong parameters

The validator catches an unrealistically high car price.

$ curl localhost:8080/car-app/read?id=401
Name: Volvo, Price: 29000

We read a car from the database.

Building the project with Maven

NetBeans saves us a lot of tedious work during development. But it is beneficial to build the project manually with Maven.

mvn archetype:generate -DgroupId=com.zetcode -DartifactId=car-app 
    -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false 

We create a new Maven project. We use the maven-archetype-webapp type.

$ tree 
.
└── car-app
    ├── pom.xml
    └── src
        └── main
            ├── resources
            └── webapp
                ├── index.jsp
                └── WEB-INF
                    └── web.xml

6 directories, 3 files

The tree command shows us the created project structure.

$ mkdir -p src/main/java/com/zetcode/ejb
$ mkdir src/main/java/com/zetcode/persistence
$ mkdir src/main/java/com/zetcode/web
$ mkdir src/main/java/com/zetcode/util
$ mkdir src/main/resources/META-INF

We create the directories.

After copying the application source files, the project structure looks like this:

$ tree
.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── zetcode
        │           ├── ejb
        │           │   ├── ManageCarBean.java
        │           │   └── ManageCarBeanLocal.java
        │           ├── persistence
        │           │   └── Car.java
        │           ├── util
        │           │   └── ValidateParameter.java
        │           └── web
        │               ├── ReadCar.java
        │               └── SaveCar.java
        ├── resources
        │   └── META-INF
        │       └── persistence.xml
        └── webapp
            ├── index.jsp
            └── WEB-INF
                └── web.xml

Don't forget to copy the web.xml file; Maven creates an empty web.xml file for us.

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zetcode</groupId>
    <artifactId>car-app</artifactId>
    <packaging>war</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>car-app Maven Webapp</name>
    <url>http://maven.apache.org</url>
   
    <dependencies>

        <dependency>
            <groupId>org.eclipse.persistence</groupId>
            <artifactId>eclipselink</artifactId>
            <version>2.5.0</version>
        </dependency>   
        
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.0</version>
        </dependency>        

        <dependency>  
            <groupId>javax</groupId>    
            <artifactId>javaee-web-api</artifactId>    
            <version>7.0</version>  
        </dependency>     
        
    </dependencies>  
    
    <build>
        
        <plugins>
            
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                <source>1.8</source>
                <target>1.8</target>
                </configuration>
            </plugin>            
            

        </plugins>    
        
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
        <finalName>car-app</finalName>
    </build>         
    
</project>

The pom.xml is a file that contains information about the project and configuration details used by Maven to build the project. We provide the dependencies for the Eclipse link persistence provider, Apache commons lang library, and the Java EE Web API. We instruct Maven to use Java 8. The final build file is called car-app.war and is located in the target subdirectory.

$ mvn package

The project is built with the mvn package command.

$ ./asadmin start-domain 

From inside the GlassFish's bin directory, we start the GlassFish server.

$ ./asadmin start-database --db-home ~/.netbeans-derby

The Derby server is started. NetBeans creates the Derby system home in the .netbeans-derby directory, which is located in the user's home directory. There we can find the sample database that we have worked with earlier.

$ ~/bin/glassfish-4.1.1/glassfish/bin/asadmin deploy target/car-app.war

We deploy the application. We see a warning saying Table/View 'CARS' already exists in Schema 'APP'. This is the consequence of the eclipselink.ddl-generation property, which was set to create_tables. We can ignore the warning.

$ curl localhost:8080/car-app/read?id=401
Name: Volvo, Price: 29000

We retrieve a car that was created earlier.

In this tutorial, we have created Enterprise JavaBeans for some simple business logic. We have used NetBeans, Derby, and Maven for our examples. ZetCode has the following related tutorials: Derby tutorial, Java tutorial, and Stripes tutorial.