bekwam courses

Beginning JPA with WildFly

January 8, 2019

The Java Persistence Architecture (JPA) is a key JavaEE technology to interact with a relational database. While you can use plain SQL with JDBC in JavaEE, some prefer the object-relational mapping (ORM) provided by JPA. An ORM simplifies your business logic by handling the serialization between your Java objects and the results returned from database queries.

The demo application is a single-table persistence function of a Contact API such as what would be used in a Customer Relationship Manager (CRM). The API will be a RESTful web service backed by an EJB. The EJB will make use of a ContactEntity. The physical model schema will be completely derived from the ContactEntity. The application will be deployed to WildFly and will use the in-memory H2 database that is installed in WildFly.

Maven pom.xml

The project will be a single WAR file containing a RESTful web service, and EJB, and a JPA Entity. For steps to set up a single-file Maven WAR in IntelliJ, follow the instructions in this article. The pom for the project is presented below


<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.bekwam</groupId>
    <artifactId>jaxrs-api-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <dependencies>

         <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>8.0</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>


</project>

The key elements of the pom.xml are the Java 8 source setting, the dependency for JavaEE 8, and the WAR directive ignoring the web.xml file. The dependency for JavaEE 8 is provided since WildFly is a JavaEE 8 compliant app server and has the API available to deployed apps.

Design

The API is a JAX-RS endpoint realized in a class called ContactResource. When working with the endpoint, a Contact object is used for JSON serialization. ContactResource delegates the data access and business logic to an EJB, CRMService. CRMService manages a single entity "ContactEntity". While you could outfit the Contact object with JPA annotation and simplify the design, this article presents distinct types to emphasize the divergence and layered separation that occurs in larger apps.

This class model shows ContactResource using CRMService for persistence. ContactResource will receive and return Contact objects from the callers. These will be in JSON form and the framework will convert automatically to the Java object. ContactResource will form ContactEntity objects and use those to communicate with CRMService.

A UML Class Diagram
JPA Demo Class Diagram

Code

ContactResource is a JAX-RS endpoint. Requests starting with the URL /contacts are received by this endpoint and dispatched to the Java methods based on the HTTP method or additional URL segments. For more information about the design of this class, see this article on API Design.

ContactResource.java


package apidemo.rs;

import apidemo.ejb.CRMService;
import apidemo.ejb.ContactEntity;

import javax.ejb.EJB;
import javax.validation.Valid;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Path("/contacts")
public class ContactsResource {

    @EJB
    private CRMService crmService;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Contact> getContacts() {
        return crmService.getAllContacts().stream().map( ce ->
            new Contact(ce.getContactId(), ce.getFirstName(), ce.getLastName(), ce.getCompanyName())
        ).collect(Collectors.toList());
    }

    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Contact getContact(@PathParam("id") Long id) {

        return crmService
                .findContact(id)
                .map(
                    entity -> new Contact(entity.getContactId(), entity.getFirstName(), entity.getLastName(), entity.getCompanyName())
                )
                .orElseThrow(() -> new WebApplicationException(Response.Status.NOT_FOUND));
    }

    @PUT
    @Path("/{id}")
    @Consumes(MediaType.APPLICATION_JSON)
    public void updateContact(@PathParam("id") Long id, Contact c) {

        crmService
                .findContact(id)
                .flatMap(
                       ce -> {
                           ce.setFirstName( c.getFirstName() );
                           ce.setLastName( c.getLastName() );
                           ce.setCompanyName( c.getCompanyName() );

                           crmService.updateContact(ce);

                           return Optional.of(ce);
                       }
                )
                .orElseThrow(() -> new WebApplicationException(Response.Status.NOT_FOUND));
    }

    @DELETE
    @Path("/{id}")
    public void deleteContact(@PathParam("id") Long id) { crmService.deleteContact(id); }

    @POST
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Contact addContact(@Valid Contact c) {

        ContactEntity ce = new ContactEntity();
        ce.setFirstName( c.getFirstName() );
        ce.setLastName( c.getLastName() );
        ce.setCompanyName( c.getCompanyName() );

        ContactEntity fromDB = crmService.addContact(ce);

        return new Contact(fromDB.getContactId(), fromDB.getFirstName(), fromDB.getLastName(), fromDB.getCompanyName());
    }
}


The EJB resource CRMService is injected into ClassResource. Simple methods like deleteContact() simply delegate to the EJB. The query methods -- getContacts() and getContact() -- need to map the ContactEntity class to the exported JSON structure by converting to Contact. addContact() and updateContact() need s similar conversion, but also on the input JSON. updateContact() and getContact() will throw NOT_FOUND exceptions.

Bean Validation is being used to verify the input to the addContact() and updateContact(). Notice the simple @Valid annotation triggers the validation behavior.

Contact.java

This is a POJO that implements Serializable and has several marked-up fields for triggering the validation.


package apidemo.rs;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

public class Contact implements Serializable {

    private Long contactId;

    @NotNull @Size(max=25)
    private String firstName;

    @NotNull
    @Size(max=25)
    private String lastName;

    @Size(max=40)
    private String companyName;

    public Contact() {
    }

    public Contact(Long contactId, String firstName, String lastName, String companyName) {
        this.contactId = contactId;
        this.firstName = firstName;
        this.lastName = lastName;
        this.companyName = companyName;
    }

    public Long getContactId() {
        return contactId;
    }

    public void setContactId(Long contactId) {
        this.contactId = contactId;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @Override
    public String toString() {
        return "Contact{" +
                "contactId=" + contactId +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", companyName='" + companyName + '\'' +
                '}';
    }
}

APIApplication.java

A small class is needed to tell the WAR file where to find the JAX-RS endpoint.


package apidemo.rs;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

@ApplicationPath("/api")
public class APIApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> set = new HashSet<>();
        set.add( ContactsResource.class );
        return set;
    }
}

ContactEntity.java

Although you could re-purpose the Contact.java class to serve as a JPA Entity, this article separates them to emphasize the likely divergence between the physical model and the API. Sometimes, there is a one-to-one correspondence between the fields in a DB table and a RESTful web service calls. Other times, the DB is created outside of JPA and we use the annotations to graft the ORM on top.

The difference isn't just separated Java code. By designating objects as @Entity, they become managed by the app server. There are rules surrounding when they are persisted and how they can be manipulated.

	
package apidemo.ejb;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Version;

@Entity
public class ContactEntity {

    @Id @GeneratedValue
    private Long contactId;

    private String firstName;
    private String lastName;
    private String companyName;

    @Version
    private Integer version;

    public Long getContactId() {
        return contactId;
    }

    public void setContactId(Long contactId) {
        this.contactId = contactId;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }
}
	
	

ContactEntity uses minimal markings to form a JPA Entity. @Entity and @Id are required. @Entity marks the class as a JPA-managed object. @Id tells JPA which field is the primary key. @GeneratedValue is a cue to the app server that this @Id will be an auto-increment column as supported by the H2 database. @Version is used for optimistic locking, throwing an error if two callers attempt to save the same object.

CRMService.java

The remaining class in the demo is an EJB, CRMService. The EJB provides transactions to the JPA operations. There are five methods in the class mapping to the RESTful calls in ContactsResource.java.

	
package apidemo.ejb;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.Optional;

@Stateless
public class CRMService {

    @PersistenceContext
    private EntityManager em;

    public List<ContactEntity> getAllContacts() {
        return em.createQuery("SELECT c FROM ContactEntity c", ContactEntity.class ).getResultList();
    }

    public Optional<ContactEntity> findContact(Long contactId) {
		return Optional.ofNullable(em.find(ContactEntity.class, contactId));
    }

    public ContactEntity addContact(ContactEntity ce) {
        em.persist( ce );
        return ce;
    }

    public void deleteContact(Long contactId) {
        em.remove( em.find(ContactEntity.class, contactId) );
    }

    public void updateContact(ContactEntity ce) { em.merge(ce); }
}	
	

To use JPA in an EJB, a @PersistenceContext is injected. A configuration file presented later will show the linkage between this declaration and the datasource set up in WildFly.

getAllContacts() contains a SQL-like expression called JPQL. It has similar clauses but is different in that it deals with Java objects rather than DB tables. There is a way to write plain SQL and still benefit from JPA if done sparingly. If SQL is a hard requirement, opt for plain JDBC.

getContact() uses the EntityManager to lookup a single record. Alternatively, I can use JPQL to SELECT a single record.

addContact(), updateContact(), and deletContact() modify the data. addContact() makes a persist() call on the passed-in Entity. deleteContact() first looks up the Entity -- em.find() -- and then removes it. updateContact() makes the call em.merge() which brings the ContactEntity back into JPA. Leaving updateContact() automatically commits the changes that have been brought over.

persistence.xml

The persistence.xml file links the injected @PersistenceContext to a datasource in WildFly. WildFly ships with the H2 database with a datasource defined in the standalone.xml file called ExampleDS; this is available out-of-the box. The hibernate.hbm2ddl.auto property is telling JPA that not only will it create data from the EJB calls, but also run DDL to create the physical model.

	
<?xml version="1.0" encoding="UTF-8"?>
<persistence 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_2.xsd"
             version="2.2">
    <persistence-unit name="myPU">
        <jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

The persistence.xml file goes in the META-INF folder. See the following screenshot for the project file structure.

IntelliJ Project Browser
Project Files

Deploying and Testing

This is a single WAR that can be deployed as described in the API design article. The screenshots from ReadyAPI show how to test the RESTful calls. If you read the API design article, notice that all of this implementation did not change the API. You can quickly build out a stub API and make that available to other teams in a larger project while you build out the implementation. This is a powerful, non-technical management technique that allows parallel development.

This article was a crash course in REST, EJB, and JPA. It was written to demonstrate the ease with which you can deploy a JavaEE app in WildFly and also to highlight the minimum of code needed for JavaEE. This design can be extended to handle large projects as the different transport objects, Entities, and EJBs can be divided into different functional areas. WildFly provides a datasource configured out-of-the-box and you can see the API working as you call the get/add/update/delete calls.


Headshot of Carl Walker

By Carl Walker

President and Principal Consultant of Bekwam, Inc