Accessing and persisting data in microservices using Java Persistence API (JPA)

duration 20 minutes

Prerequisites:

Learn how to use Java Persistence API (JPA) to access and persist data to a database for your microservices.

What you’ll learn

You will learn how to use the Java Persistence API (JPA) to map Java objects to relational database tables and perform create, read, update and delete (CRUD) operations on the data in your microservices.

JPA is a Java EE specification for representing relational database table data as Plain Old Java Objects (POJO). JPA simplifies object-relational mapping (ORM) by using annotations to map Java objects to tables in a relational database. In addition to providing an efficient API for performing CRUD operations, JPA also reduces the burden of having to write JDBC and SQL code when performing database operations and takes care of database vendor-specific differences. This capability allows you to focus on the business logic of your application instead of wasting time implementing repetitive CRUD logic.

The application that you will be working with is an event manager, which is composed of a UI and an event microservice for creating, retrieving, updating, and deleting events. In this guide, you will be focused on the event microservice. The event microservice consists of a JPA entity class whose fields will be persisted to a database. The database logic is implemented in a Data Access Object (DAO) to isolate the database operations from the rest of the service. This DAO accesses and persists JPA entities to the database and can be injected and consumed by other components in the microservice. An Embedded Derby database is used as a data store for all the events.

You will use JPA annotations to define an entity class whose fields are persisted to the database. The interaction between your service and the database is mediated by the persistence context that is managed by an entity manager. In a Java EE environment, you can use an application-managed entity manager or a container-managed entity manager. In this guide, you will use a container-managed entity manager that is injected into the DAO so the application server manages the opening and closing of the entity manager for you.

Getting started

The fastest way to work through this guide is to clone the Git repository and use the projects that are provided inside:

git clone https://github.com/openliberty/guide-jpa-intro.git
cd guide-jpa-intro

The start directory contains the starting project that you will build upon.

The finish directory contains the finished project that you will build.

Try what you’ll build

The finish directory in the root of this guide contains the finished application. Give it a try before you proceed.

To try out the application, first navigate to the finish directory and then run the following Maven goal to build the application and run it inside Open Liberty:

cd finish
mvn install liberty:start-server

Point your browser to the http://localhost:9090/eventmanager.jsf URL. The event application does not display any events because no events are stored in the database. Go ahead and click Create Event, located in the left navigation bar. After entering an event name, location and time, click Submit to persist your event entity to the database. The event is now stored in the database and is visible in the list of current events.

Notice that if you stop the Open Liberty server and then restart it, the events created are still displayed in the list of current events. Run the following Maven goals to stop and then restart the server:

mvn liberty:stop-server
mvn liberty:start-server

The events created are still displayed in the list of current events. The Update action link located beside each event allows you to make modifications to the persisted entity and the Delete action link allows you to remove entities from the database.

After you are done checking out the application, stop the Open Liberty server:

mvn liberty:stop-server

Defining a JPA entity class

Navigate to the start directory to begin.

To store Java objects in a database, you must define a JPA entity class. A JPA entity is a Java object whose non-transient and non-static fields will be persisted to the database. Any Plain Old Java Object (POJO) class can be designated as a JPA entity. However, the class must be annotated with the @Entity annotation, must not be declared final and must have a public or protected non-argument constructor. JPA maps an entity type to a database table and persisted instances will be represented as rows in the table.

The Event class is a data model that represents events in the event microservice and is annotated with JPA annotations.

Create the Event class.
backendServices/src/main/java/io/openliberty/guides/event/models/Event.java

Event.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2018, 2020 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13package io.openliberty.guides.event.models;
 14
 15import java.io.Serializable;
 16import javax.persistence.Entity;
 17import javax.persistence.Table;
 18import javax.persistence.NamedQuery;
 19import javax.persistence.GeneratedValue;
 20import javax.persistence.Id;
 21import javax.persistence.Column;
 22import javax.persistence.GenerationType;
 23
 24// tag::Entity[]
 25@Entity
 26// end::Entity[]
 27// tag::Table[]
 28@Table(name = "Event")
 29// end::Table[]
 30// tag::NamedQuery[]
 31@NamedQuery(name = "Event.findAll", query = "SELECT e FROM Event e")
 32@NamedQuery(name = "Event.findEvent", query = "SELECT e FROM Event e WHERE "
 33    + "e.name = :name AND e.location = :location AND e.time = :time")
 34// end::NamedQuery[]
 35// tag::Event[]
 36public class Event implements Serializable {
 37    private static final long serialVersionUID = 1L;
 38
 39    // tag::GeneratedValue[]
 40    @GeneratedValue(strategy = GenerationType.AUTO)
 41    // end::GeneratedValue[]
 42    // tag::Id[]
 43    @Id
 44    // end::Id[]
 45    // tag::Column[]
 46    @Column(name = "eventId")
 47    // end::Column[]
 48    private int id;
 49
 50    @Column(name = "eventLocation")
 51    private String location;
 52    @Column(name = "eventTime")
 53    private String time;
 54    @Column(name = "eventName")
 55    private String name;
 56
 57    public Event() {
 58    }
 59
 60    public Event(String name, String location, String time) {
 61        this.name = name;
 62        this.location = location;
 63        this.time = time;
 64    }
 65
 66    public int getId() {
 67        return id;
 68    }
 69
 70    public void setId(int id) {
 71        this.id = id;
 72    }
 73
 74    public String getLocation() {
 75        return location;
 76    }
 77
 78    public void setLocation(String location) {
 79        this.location = location;
 80    }
 81
 82    public String getTime() {
 83        return time;
 84    }
 85
 86    public void setTime(String time) {
 87        this.time = time;
 88    }
 89
 90    public void setName(String name) {
 91        this.name = name;
 92    }
 93
 94    public String getName() {
 95        return name;
 96    }
 97
 98    @Override
 99    public int hashCode() {
100        final int prime = 31;
101        int result = 1;
102        result = prime * result + id;
103        result = prime * result + ((location == null) ? 0 : location.hashCode());
104        result = prime * result + ((name == null) ? 0 : name.hashCode());
105        result = prime * result +
106                 (int) (serialVersionUID ^ (serialVersionUID >>> 32));
107        result = prime * result + ((time == null) ? 0 : time.hashCode());
108        return result;
109    }
110
111    @Override
112    public boolean equals(Object obj) {
113        if (this == obj) {
114            return true;
115        }
116        if (obj == null) {
117            return false;
118        }
119        if (getClass() != obj.getClass()) {
120            return false;
121        }
122        Event other = (Event) obj;
123        if (location == null) {
124            if (other.location != null) {
125                return false;
126            }
127        } else if (!location.equals(other.location)) {
128            return false;
129        }
130        if (time == null) {
131            if (other.time != null) {
132                return false;
133            }
134        } else if (!time.equals(other.time)) {
135            return false;
136        }
137        if (name == null) {
138            if (other.name != null) {
139                return false;
140            }
141        } else if (!name.equals(other.name)) {
142            return false;
143        }
144
145        return true;
146    }
147
148    @Override
149    public String toString() {
150        return "Event [name=" + name + ", location=" + location + ", time=" + time
151                + "]";
152    }
153}
154// end::Event[]

The following table breaks down the new annotations:

AnnotationDescription

@Entity

Declares the class as an entity

@Table

Specifies details of the table such as name

@NamedQuery

Specifies a predefined database query that is run by an EntityManager instance.

@Id

Declares the primary key of the entity

@GeneratedValue

Specifies the strategy used for generating the value of the primary key. The strategy = GenerationType.AUTO code indicates that the generation strategy is automatically selected

@Column

Specifies that the field is mapped to a column in the database table. The name attribute is optional and indicates the name of the column in the table

Configuring JPA

The persistence.xml file is a configuration file that defines a persistence unit. The persistence unit specifies configuration information for the entity manager.

Create the configuration file.
backendServices/src/main/resources/META-INF/persistence.xml

persistence.xml

 1<?xml version="1.0" encoding="UTF-8"?>
 2<persistence version="2.2"
 3    xmlns="http://xmlns.jcp.org/xml/ns/persistence"
 4    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 5    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
 6                        http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
 7    <!-- tag::persistence-unit[] -->
 8    <!-- tag::transaction-type[] -->
 9    <persistence-unit name="jpa-unit" transaction-type="JTA">
10    <!-- end::transaction-type[] -->
11        <!-- tag::jta-data[] -->
12        <jta-data-source>jdbc/eventjpadatasource</jta-data-source>
13        <!-- end::jta-data[] -->
14        <properties>
15        <!-- tag::eclipse-link[] -->
16            <property name="eclipselink.ddl-generation" value="create-tables"/>
17            <property name="eclipselink.ddl-generation.output-mode" value="both" />
18        <!-- end::eclipse-link[] -->
19        </properties>
20    </persistence-unit>
21    <!-- end::persistence-unit[] -->
22</persistence>

The persistence unit is defined by the <persistence-unit/> XML element. The name attribute is required and is used to identify the persistent unit when using the @PersistenceContext annotation to inject the entity manager later in this guide. The transaction-type="JTA" attribute specifies to use Java Transaction API (JTA) transaction management. Since we are using a container-managed entity manager, JTA transactions must be used.

A JTA transaction type requires a JTA data source to be provided. The <jta-data-source/> element specifies the Java Naming and Directory Interface (JNDI) name of the data source that is used. The data source has already been configured for you in the backendServices/src/main/liberty/config/server.xml file. This data source configuration is where the Java Database Connectivity (JDBC) connection is defined along with some database vendor-specific properties.

server.xml

 1<server description="Sample Liberty server">
 2
 3  <featureManager>
 4    <feature>jaxrs-2.1</feature>
 5    <feature>jsonp-1.1</feature>
 6    <feature>cdi-2.0</feature>
 7    <feature>jpa-2.2</feature>
 8  </featureManager>
 9
10  <httpEndpoint httpPort="${default.http.port}" httpsPort="${default.https.port}"
11    id="defaultHttpEndpoint" host="*" />
12
13  <application location="backendServices.war" type="war" context-root="/">
14  </application>
15
16  <!-- Derby Library Configuration -->
17  <library id="derbyJDBCLib">
18      <fileset dir="${shared.resource.dir}" includes="derby*.jar"/>
19  </library>
20
21  <!-- Datasource Configuration -->
22  <!-- tag::data-source[] -->
23  <dataSource id="eventjpadatasource"
24              jndiName="jdbc/eventjpadatasource">
25      <jdbcDriver libraryRef="derbyJDBCLib" />
26      <properties.derby.embedded databaseName="EventDB" createDatabase="create"/>
27  </dataSource>
28  <!-- end::data-source[] -->
29
30</server>

The eclipselink.ddl-generation properties are used here so that you aren’t required to manually create a database table to run this sample application. To learn more about the ddl-generation properties, see the JPA Extensions Reference for EclipseLink.

Performing CRUD operations using JPA

The CRUD operations are defined in the DAO. To perform these operations by using JPA, we need an EventDao class.

Create the EventDao class.
backendServices/src/main/java/io/openliberty/guides/event/dao/EventDao.java

EventDao.java

 1// tag::copyright[]
 2/*******************************************************************************
 3 * Copyright (c) 2018, 2020 IBM Corporation and others.
 4 * All rights reserved. This program and the accompanying materials
 5 * are made available under the terms of the Eclipse Public License v1.0
 6 * which accompanies this distribution, and is available at
 7 * http://www.eclipse.org/legal/epl-v10.html
 8 *
 9 * Contributors:
10 *     IBM Corporation - Initial implementation
11 *******************************************************************************/
12// end::copyright[]
13package io.openliberty.guides.event.dao;
14
15import java.util.List;
16import javax.persistence.EntityManager;
17import javax.persistence.PersistenceContext;
18
19import io.openliberty.guides.event.models.Event;
20
21import javax.enterprise.context.RequestScoped;
22
23@RequestScoped
24// tag::EventDao[]
25public class EventDao {
26
27    // tag::PersistenceContext[]
28    @PersistenceContext(name = "jpa-unit")
29    // end::PersistenceContext[]
30    private EntityManager em;
31
32    // tag::createEvent[]
33    public void createEvent(Event event) {
34        // tag::Persist[]
35        em.persist(event);
36        // end::Persist[]
37    }
38    // end::createEvent[]
39
40    // tag::readEvent[]
41    public Event readEvent(int eventId) {
42        // tag::Find[]
43        return em.find(Event.class, eventId);
44        // end::Find[]
45    }
46    // end::readEvent[]
47
48    // tag::updateEvent[]
49    public void updateEvent(Event event) {
50        // tag::Merge[]
51        em.merge(event);
52        // end::Merge[]
53    }
54    // end::updateEvent[]
55
56    // tag::deleteEvent[]
57    public void deleteEvent(Event event) {
58        // tag::Remove[]
59        em.remove(event);
60        // end::Remove[]
61    }
62    // end::deleteEvent[]
63
64    // tag::readAllEvents[]
65    public List<Event> readAllEvents() {
66        return em.createNamedQuery("Event.findAll", Event.class).getResultList();
67    }
68    // end::readAllEvents[]
69
70    // tag::findEvent[]
71    public List<Event> findEvent(String name, String location, String time) {
72        return em.createNamedQuery("Event.findEvent", Event.class)
73            .setParameter("name", name)
74            .setParameter("location", location)
75            .setParameter("time", time).getResultList();
76    }
77    // end::findEvent[]
78}
79// end::EventDao[]

To use the entity manager at runtime, inject it into our CDI bean through the @PersistenceContext annotation. The entity manager interacts with the persistence context. Every EntityManager instance is associated with a persistence context. The persistence context manages a set of entities and is aware of the different states that an entity can have. The persistence context synchronizes with the database when a transaction commits.

The EventDao class has a method for each CRUD operation, so let’s break them down:

  • The createEvent() method persists an instance of the Event entity class to the data store by calling the persist() method on an EntityManager instance. The entity instance becomes managed and changes to it will be tracked by the entity manager.

  • The readEvent() method returns an instance of the Event entity class with the specified primary key by calling the find() method on an EntityManager instance. If the event instance is found, it is returned in a managed state, but, if the event instance is not found, null is returned.

  • The readAllEvents() method demonstrates an alternative way to retrieve event objects from the database. This method returns a list of instances of the Event entity class by using the Event.findAll query specified in the @NamedQuery annotation on the Event class. Similarly, the findEvent() method uses the Event.findEvent named query to find an event with the given name, location and time.

Event.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2018, 2020 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13package io.openliberty.guides.event.models;
 14
 15import java.io.Serializable;
 16import javax.persistence.Entity;
 17import javax.persistence.Table;
 18import javax.persistence.NamedQuery;
 19import javax.persistence.GeneratedValue;
 20import javax.persistence.Id;
 21import javax.persistence.Column;
 22import javax.persistence.GenerationType;
 23
 24// tag::Entity[]
 25@Entity
 26// end::Entity[]
 27// tag::Table[]
 28@Table(name = "Event")
 29// end::Table[]
 30// tag::NamedQuery[]
 31@NamedQuery(name = "Event.findAll", query = "SELECT e FROM Event e")
 32@NamedQuery(name = "Event.findEvent", query = "SELECT e FROM Event e WHERE "
 33    + "e.name = :name AND e.location = :location AND e.time = :time")
 34// end::NamedQuery[]
 35// tag::Event[]
 36public class Event implements Serializable {
 37    private static final long serialVersionUID = 1L;
 38
 39    // tag::GeneratedValue[]
 40    @GeneratedValue(strategy = GenerationType.AUTO)
 41    // end::GeneratedValue[]
 42    // tag::Id[]
 43    @Id
 44    // end::Id[]
 45    // tag::Column[]
 46    @Column(name = "eventId")
 47    // end::Column[]
 48    private int id;
 49
 50    @Column(name = "eventLocation")
 51    private String location;
 52    @Column(name = "eventTime")
 53    private String time;
 54    @Column(name = "eventName")
 55    private String name;
 56
 57    public Event() {
 58    }
 59
 60    public Event(String name, String location, String time) {
 61        this.name = name;
 62        this.location = location;
 63        this.time = time;
 64    }
 65
 66    public int getId() {
 67        return id;
 68    }
 69
 70    public void setId(int id) {
 71        this.id = id;
 72    }
 73
 74    public String getLocation() {
 75        return location;
 76    }
 77
 78    public void setLocation(String location) {
 79        this.location = location;
 80    }
 81
 82    public String getTime() {
 83        return time;
 84    }
 85
 86    public void setTime(String time) {
 87        this.time = time;
 88    }
 89
 90    public void setName(String name) {
 91        this.name = name;
 92    }
 93
 94    public String getName() {
 95        return name;
 96    }
 97
 98    @Override
 99    public int hashCode() {
100        final int prime = 31;
101        int result = 1;
102        result = prime * result + id;
103        result = prime * result + ((location == null) ? 0 : location.hashCode());
104        result = prime * result + ((name == null) ? 0 : name.hashCode());
105        result = prime * result +
106                 (int) (serialVersionUID ^ (serialVersionUID >>> 32));
107        result = prime * result + ((time == null) ? 0 : time.hashCode());
108        return result;
109    }
110
111    @Override
112    public boolean equals(Object obj) {
113        if (this == obj) {
114            return true;
115        }
116        if (obj == null) {
117            return false;
118        }
119        if (getClass() != obj.getClass()) {
120            return false;
121        }
122        Event other = (Event) obj;
123        if (location == null) {
124            if (other.location != null) {
125                return false;
126            }
127        } else if (!location.equals(other.location)) {
128            return false;
129        }
130        if (time == null) {
131            if (other.time != null) {
132                return false;
133            }
134        } else if (!time.equals(other.time)) {
135            return false;
136        }
137        if (name == null) {
138            if (other.name != null) {
139                return false;
140            }
141        } else if (!name.equals(other.name)) {
142            return false;
143        }
144
145        return true;
146    }
147
148    @Override
149    public String toString() {
150        return "Event [name=" + name + ", location=" + location + ", time=" + time
151                + "]";
152    }
153}
154// end::Event[]
  • The updateEvent() method creates a managed instance of a detached entity instance. The entity manager automatically tracks all managed entity objects in its persistence context for changes and synchronizes them with the database. However, if an entity becomes detached, you must merge that entity into the persistence context by calling the merge() method so that changes to loaded fields of the detached entity are tracked.

  • The deleteEvent() method removes an instance of the Event entity class from the database by calling the remove() method on an EntityManager instance. The state of the entity is changed to removed and is removed from the database upon transaction commit.

The DAO is injected into the backendServices/src/main/java/io/openliberty/guides/event/resources/EventResource.java class and used to access and persist data. The @Transactional annotation is used in the EventResource class to declaratively control the transaction boundaries on the @RequestScoped CDI bean. This ensures that the methods run within the boundaries of an active global transaction, which is why it is not necessary to explicitly begin, commit or rollback transactions. At the end of the transactional method invocation, the transaction commits and the persistence context flushes any changes to Event entity instances it is managing to the database.

EventResource.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2018, 2020 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13package io.openliberty.guides.event.resources;
 14
 15import javax.json.Json;
 16import javax.json.JsonArray;
 17import javax.json.JsonObject;
 18import javax.json.JsonArrayBuilder;
 19import javax.json.JsonObjectBuilder;
 20import javax.transaction.Transactional;
 21import javax.ws.rs.Consumes;
 22import javax.ws.rs.FormParam;
 23import javax.ws.rs.GET;
 24import javax.ws.rs.PUT;
 25import javax.ws.rs.POST;
 26import javax.ws.rs.DELETE;
 27import javax.ws.rs.Path;
 28import javax.ws.rs.PathParam;
 29import javax.ws.rs.Produces;
 30import javax.ws.rs.core.MediaType;
 31import javax.ws.rs.core.Response;
 32import javax.enterprise.context.RequestScoped;
 33import javax.inject.Inject;
 34
 35import io.openliberty.guides.event.dao.EventDao;
 36import io.openliberty.guides.event.models.Event;
 37
 38// tag::RequestedScoped[]
 39@RequestScoped
 40// end::RequestedScoped[]
 41@Path("events")
 42// tag::DAO[]
 43// tag::EventResource[]
 44public class EventResource {
 45
 46    @Inject
 47    private EventDao eventDAO;
 48
 49    /**
 50     * This method creates a new event from the submitted data (name, time and
 51     * location) by the user.
 52     */
 53    @POST
 54    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
 55    // tag::Transactional[]
 56    @Transactional
 57    // end::Transactional[]
 58    public Response addNewEvent(@FormParam("name") String name,
 59        @FormParam("time") String time, @FormParam("location") String location) {
 60        Event newEvent = new Event(name, location, time);
 61        if(!eventDAO.findEvent(name, location, time).isEmpty()) {
 62            return Response.status(Response.Status.BAD_REQUEST)
 63                           .entity("Event already exists").build();
 64        }
 65        eventDAO.createEvent(newEvent);
 66        return Response.status(Response.Status.NO_CONTENT).build();
 67    }
 68
 69    /**
 70     * This method updates a new event from the submitted data (name, time and
 71     * location) by the user.
 72     */
 73    @PUT
 74    @Path("{id}")
 75    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
 76    @Transactional
 77    public Response updateEvent(@FormParam("name") String name,
 78        @FormParam("time") String time, @FormParam("location") String location,
 79        @PathParam("id") int id) {
 80        Event prevEvent = eventDAO.readEvent(id);
 81        if(prevEvent == null) {
 82            return Response.status(Response.Status.NOT_FOUND)
 83                           .entity("Event does not exist").build();
 84        }
 85        if(!eventDAO.findEvent(name, location, time).isEmpty()) {
 86            return Response.status(Response.Status.BAD_REQUEST)
 87                           .entity("Event already exists").build();
 88        }
 89        prevEvent.setName(name);
 90        prevEvent.setLocation(location);
 91        prevEvent.setTime(time);
 92
 93        eventDAO.updateEvent(prevEvent);
 94        return Response.status(Response.Status.NO_CONTENT).build();
 95    }
 96
 97    /**
 98     * This method deletes a specific existing/stored event
 99     */
100    @DELETE
101    @Path("{id}")
102    @Transactional
103    public Response deleteEvent(@PathParam("id") int id) {
104        Event event = eventDAO.readEvent(id);
105        if(event == null) {
106            return Response.status(Response.Status.NOT_FOUND)
107                           .entity("Event does not exist").build();
108        }
109        eventDAO.deleteEvent(event);
110        return Response.status(Response.Status.NO_CONTENT).build();
111    }
112
113    /**
114     * This method returns a specific existing/stored event in Json format
115     */
116    @GET
117    @Path("{id}")
118    @Produces(MediaType.APPLICATION_JSON)
119    @Transactional
120    public JsonObject getEvent(@PathParam("id") int eventId) {
121        JsonObjectBuilder builder = Json.createObjectBuilder();
122        Event event = eventDAO.readEvent(eventId);
123        if(event != null) {
124            builder.add("name", event.getName()).add("time", event.getTime())
125                .add("location", event.getLocation()).add("id", event.getId());
126        }
127        return builder.build();
128    }
129
130    /**
131     * This method returns the existing/stored events in Json format
132     */
133    @GET
134    @Produces(MediaType.APPLICATION_JSON)
135    @Transactional
136    public JsonArray getEvents() {
137        JsonObjectBuilder builder = Json.createObjectBuilder();
138        JsonArrayBuilder finalArray = Json.createArrayBuilder();
139        for (Event event : eventDAO.readAllEvents()) {
140            builder.add("name", event.getName()).add("time", event.getTime())
141                   .add("location", event.getLocation()).add("id", event.getId());
142            finalArray.add(builder.build());
143        }
144        return finalArray.build();
145    }
146}
147// end::DAO[]
148// end::EventResource[]

Building and running the application

To build the application, run the Maven install phase from the command line in the start directory:

mvn install

This command builds the application and creates a .war file in the target directory. It also configures and installs Open Liberty into the target/liberty/wlp directory.

Next, run the Maven liberty:start-server goal:

mvn liberty:start-server

This goal starts an Open Liberty server instance. Your Maven pom.xml is already configured to start the application in this server instance.

When the server is running, go to the http://localhost:9090/eventmanager.jsf URL to view the Event Manager application.

Click Create Event in the left navigation bar to create events that are persisted to the database. After you create an event, it is available to view, update, and delete in the Current Events section.

When you are done checking out the services, stop the Open Liberty server by running the following command:

mvn liberty:stop-server

Testing the application

Create the EventEntityTest class.
backendServices/src/test/java/it/io/openliberty/guides/event/EventEntityTest.java

EventEntityTest.java

  1// tag::copyright[]
  2/*******************************************************************************
  3 * Copyright (c) 2018, 2020 IBM Corporation and others.
  4 * All rights reserved. This program and the accompanying materials
  5 * are made available under the terms of the Eclipse Public License v1.0
  6 * which accompanies this distribution, and is available at
  7 * http://www.eclipse.org/legal/epl-v10.html
  8 *
  9 * Contributors:
 10 *     IBM Corporation - Initial implementation
 11 *******************************************************************************/
 12// end::copyright[]
 13package it.io.openliberty.guides.event;
 14
 15import static org.junit.Assert.assertEquals;
 16
 17import java.util.HashMap;
 18import javax.json.JsonObject;
 19import javax.ws.rs.client.ClientBuilder;
 20import javax.ws.rs.core.Form;
 21import javax.ws.rs.core.Response.Status;
 22
 23import org.apache.cxf.jaxrs.provider.jsrjsonp.JsrJsonpProvider;
 24import org.junit.After;
 25import org.junit.Before;
 26import org.junit.BeforeClass;
 27import org.junit.Test;
 28import io.openliberty.guides.event.models.Event;
 29
 30public class EventEntityTest extends EventTest {
 31
 32    private static final String JSONFIELD_LOCATION = "location";
 33    private static final String JSONFIELD_NAME = "name";
 34    private static final String JSONFIELD_TIME = "time";
 35    private static final String JSONFIELD_ID = "id";
 36    private static final String EVENT_TIME = "12:00 PM, January 1 2018";
 37    private static final String EVENT_LOCATION = "IBM";
 38    private static final String EVENT_NAME = "JPA Guide";
 39    private static final String UPDATE_EVENT_TIME = "12:00 PM, February 1 2018";
 40    private static final String UPDATE_EVENT_LOCATION = "IBM Updated";
 41    private static final String UPDATE_EVENT_NAME = "JPA Guide Updated";
 42
 43    private static final int NO_CONTENT_CODE = Status.NO_CONTENT.getStatusCode();
 44    private static final int NOT_FOUND_CODE = Status.NOT_FOUND.getStatusCode();
 45
 46    @BeforeClass
 47    public static void oneTimeSetup() {
 48        port = System.getProperty("backend.http.port");
 49        baseUrl = "http://localhost:" + port + "/";
 50    }
 51
 52    @Before
 53    public void setup() {
 54        form = new Form();
 55        client = ClientBuilder.newClient();
 56        client.register(JsrJsonpProvider.class);
 57
 58        eventForm = new HashMap<String, String>();
 59
 60        eventForm.put(JSONFIELD_NAME, EVENT_NAME);
 61        eventForm.put(JSONFIELD_LOCATION, EVENT_LOCATION);
 62        eventForm.put(JSONFIELD_TIME, EVENT_TIME);
 63    }
 64
 65    @Test
 66    // tag::testInvalidRead[]
 67    public void testInvalidRead() {
 68        assertEquals("Reading an event that does not exist should return an empty list",
 69            true, getIndividualEvent(-1).isEmpty());
 70    }
 71    // end::testInvalidRead[]
 72
 73    @Test
 74    // tag::testInvalidDelete[]
 75    public void testInvalidDelete() {
 76        int deleteResponse = deleteRequest(-1);
 77        assertEquals("Trying to delete an event that does not exist should return the "
 78            + "HTTP response code " + NOT_FOUND_CODE, NOT_FOUND_CODE, deleteResponse);
 79    }
 80    // end::testInvalidDelete[]
 81
 82    @Test
 83    // tag::testInvalidUpdate[]
 84    public void testInvalidUpdate() {
 85        int updateResponse = updateRequest(eventForm, -1);
 86        assertEquals("Trying to update an event that does not exist should return the "
 87            + "HTTP response code " + NOT_FOUND_CODE, NOT_FOUND_CODE, updateResponse);
 88    }
 89    // end::testInvalidUpdate[]
 90
 91    @Test
 92    // tag::testReadIndividualEvent[]
 93    public void testReadIndividualEvent() {
 94        int postResponse = postRequest(eventForm);
 95        assertEquals("Creating an event should return the HTTP reponse code " +
 96            NO_CONTENT_CODE, NO_CONTENT_CODE, postResponse);
 97
 98        Event e = new Event(EVENT_NAME, EVENT_LOCATION, EVENT_TIME);
 99        JsonObject event = findEvent(e);
100        event = getIndividualEvent(event.getInt("id"));
101        assertData(event, EVENT_NAME, EVENT_LOCATION, EVENT_TIME);
102
103        int deleteResponse = deleteRequest(event.getInt("id"));
104        assertEquals("Deleting an event should return the HTTP response code " +
105            NO_CONTENT_CODE, NO_CONTENT_CODE, deleteResponse);
106    }
107    // end::testReadIndividualEvent[]
108
109    @Test
110    // tag::testCURD[]
111    public void testCRUD() {
112        int eventCount = getRequest().size();
113        int postResponse = postRequest(eventForm);
114        assertEquals("Creating an event should return the HTTP reponse code " +
115            NO_CONTENT_CODE, NO_CONTENT_CODE, postResponse);
116
117        Event e = new Event(EVENT_NAME, EVENT_LOCATION, EVENT_TIME);
118        JsonObject event = findEvent(e);
119        assertData(event, EVENT_NAME, EVENT_LOCATION, EVENT_TIME);
120
121        eventForm.put(JSONFIELD_NAME, UPDATE_EVENT_NAME);
122        eventForm.put(JSONFIELD_LOCATION, UPDATE_EVENT_LOCATION);
123        eventForm.put(JSONFIELD_TIME, UPDATE_EVENT_TIME);
124        int updateResponse = updateRequest(eventForm, event.getInt("id"));
125        assertEquals("Updating an event should return the HTTP response code " +
126            NO_CONTENT_CODE, NO_CONTENT_CODE, updateResponse);
127
128        e = new Event(UPDATE_EVENT_NAME, UPDATE_EVENT_LOCATION, UPDATE_EVENT_TIME);
129        event = findEvent(e);
130        assertData(event, UPDATE_EVENT_NAME, UPDATE_EVENT_LOCATION, UPDATE_EVENT_TIME);
131
132        int deleteResponse = deleteRequest(event.getInt("id"));
133        assertEquals("Deleting an event should return the HTTP response code " +
134            NO_CONTENT_CODE, NO_CONTENT_CODE, deleteResponse);
135        assertEquals("Total number of events stored should be the same after testing "
136            + "CRUD operations.", eventCount, getRequest().size());
137    }
138    // end::testCURD[]
139
140    @After
141    public void teardown() {
142        response.close();
143        client.close();
144    }
145
146}

The testInvalidRead(), testInvalidDelete() and testInvalidUpdate() methods use a primary key that is not in the database to test reading, updating and deleting an event that does not exist, respectively.

The testReadIndividualEvent() method persists a test event to the database and retrieves the event object from the database using the primary key of the entity.

The testCRUD() method creates a test event and persists it to the database. The event object is then retrieved from the database to verify that the test event was actually persisted. Next, the name, location, and time of the test event are updated. The event object is retrieved from the database to verify that the updated event is stored. Finally, the updated test event is deleted and one final check is done to ensure that the updated test event is no longer stored in the database.

Running the tests

If the server is still running from the previous steps, stop it using the Maven liberty:stop-server goal from command line in the start directory:

mvn liberty:stop-server

Then, verify that the tests pass using the Maven verify goal:

mvn verify

It may take some time before build is complete. If the tests pass, you will see a similar output to the following:

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running it.io.openliberty.guides.event.EventEntityTest
Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.703 sec - in it.io.openliberty.guides.event.EventEntityTest

Results :

Tests run: 5, Failures: 0, Errors: 0, Skipped: 0

Great work! You’re done!

You learned how to map Java objects to database tables by defining a JPA entity class whose instances are represented as rows in the table. You have injected a container-managed entity manager into a DAO and learned how to perform CRUD operations in your microservice in Open Liberty.

Guide Attribution

Accessing and persisting data in microservices using Java Persistence API (JPA) by Open Liberty is licensed under CC BY-ND 4.0

Copied to clipboard
Copy code block
Copy file contents

Prerequisites:

Nice work! Where to next?

What did you think of this guide?

Extreme Dislike Dislike Like Extreme Like

What could make this guide better?

Raise an issue to share feedback

Create a pull request to contribute to this guide

Need help?

Ask a question on Stack Overflow

Like Open Liberty? Star our repo on GitHub.

Star