In this and the following articles, I'm going to build a TomEE application that provides a REST interface. The interface will then be queried by a React JavaScript frontend. Specifically, the back end will expose a REST interface via JAX-RS providing information about Constellations. The React front end will allow users to select a Constellation to get more details about it. The final product will look something like:
Creating The Back End
The quickest way to create the Constellations API is to use the MicroProfile Starter. This enbles all the scaffolding to be created for a MicroProfile Maven application. For this application the followng options were chosen:
-
groupId
- com.davidsalter -
artifactId
- constellationapi -
MicroProfile Version
- 2.1 -
MicroProfile Server
- TomEE
At the time of writing, the latest available version of TomEE on the MicroProfile Starter is 8.0.0.M3, however version 8.0.0 has been released since the starter was last updated. The first task therefore, is to update to the correct (and latest) version of TomEE in the project's pom.xml file.
<properties>
<tomee.version>8.0.0</tomee.version>
...
<plugin>
<groupId>org.apache.tomee.maven</groupId>
<artifactId>tomee-maven-plugin</artifactId>
<version>${tomee.version}</version>
To easily access the constellations within the API, I want to store them in a database and query them using JPA. To do this, I need to add some of Java EE 8 into the application. This is achieved by adding the javaee-api
dependency into the pom.xml
<dependency>
<groupId>org.apache.tomee</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0-2</version>
<scope>provided</scope>
</dependency>
The full pom.xml
looks like:
<?xml version="1.0" encoding="UTF-8"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>com.davidsalter</groupId>
<artifactId>constellationapi</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<final.name>constellationapi</final.name>
<tomee.version>8.0.0</tomee.version>
<failOnMissingWebXml>false</failOnMissingWebXml>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.microprofile</groupId>
<artifactId>microprofile</artifactId>
<version>2.1</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomee</groupId>
<artifactId>javaee-api</artifactId>
<version>8.0-2</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>constellationapi</finalName>
</build>
<profiles>
<profile>
<id>tomee</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.tomee.maven</groupId>
<artifactId>tomee-maven-plugin</artifactId>
<version>${tomee.version}</version>
<executions>
<execution>
<id>executable-jar</id>
<phase>package</phase>
<goals>
<goal>exec</goal>
</goals>
</execution>
</executions>
<configuration>
<context>ROOT</context>
<tomeeClassifier>microprofile</tomeeClassifier>
<tomeeHttpPort>8080</tomeeHttpPort>
<tomeeShutdownPort>8005</tomeeShutdownPort>
<tomeeAjpPort>8009</tomeeAjpPort>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Now that we've created the basic scaffolding for the project, using the MicroProfile Starter and manually editing the pom.xml
, there are 3 things we need to do to complete the API.
- Create a persistence layer to hold details of all the Constellations
- Populate the persistence layer
- Create a JAX-RS endpoint to query the persistence layer
Create A Persistence Layer
Since we're using JPA, we need to create a @Entity
class to represent a Constellation and a DAO class to use to retrieve data from the backend database table. Since I'm using TomEE, I can use the embedded HSQLDB and don't need to install any new drivers. All the required dependencies are provided out of the box.
First off, we need to create an @Entity
class
package com.davidsalter.constellationapi.entity;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity
@Table(name = "Constellation")
@NamedQuery(name = "Constellation.findAll", query = "SELECT c FROM Constellation c")
public class Constellation {
private static final long serialVersionUID = 1L;
public Constellation() {
}
private String name;
@Id
private String abbreviation;
private String description;
// Getters / Setters omitted
From this code, we can see that an @Entity
class has been defined that is backed off by a database table called Constellation
This table has 3 fields
Field | Description |
---|---|
name | The name of a constellation. |
abbreviation | The abbreviation for the constellation. This is the PK |
description | The description for the constellation, |
The class also defines a single @NamedQuery
to get a list of all of the Constellations form the database.
So, the Constellation
class represents a single entity, so we need to write a DAO class to be able to retrieve all the entities from the database.
The ConstellationDao
class looks like the following:
package com.davidsalter.constellationapi.controller;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.davidsalter.constellationapi.entity.Constellation;
@RequestScoped
public class ConstellationDao {
@PersistenceContext(name = "constellation-pu")
private EntityManager em;
public List<Constellation> findAllConstellations() {
return em.createNamedQuery("Constellation.findAll", Constellation.class).getResultList();
}
}
This class simply provides one method (findAllConstellations
) that executes the @NamedQuery
we created within the Constellation
class.
The class makes use of an EntityManager
to query the database. This in turn uses a persistence unit called constellation-pu
to define the database and how it is created.
Within the META-INF/persistence.xml
file, we define the persistence unit as follows:
<persistence version="2.2"
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_1.xsd">
<persistence-unit name="constellation-pu">
<provider>org.apache.openjpa.persistence.PersistenceProviderImpl
</provider>
<jta-data-source>constellationDatasource</jta-data-source>
<properties>
<property name="openjpa.jdbc.DBDictionary" value="hsql" />
<property
name="javax.persistence.schema-generation.database.action"
value="drop-and-create" />
<property
name="javax.persistence.schema-generation.create-source"
value="metadata" />
<property
name="javax.persistence.schema-generation.drop-source"
value="metadata" />
<property name="javax.persistence.sql-load-script-source"
value="META-INF/import.sql" />
</properties>
</persistence-unit>
</persistence>
This persistence unit specifies that it uses a JTA data source called constellationDatasource
. We'll see how this is defined in a minute. For now though, lets look at the properties that we define for the data source.
First of all, we tell OpenJPA that we are using a HSQL database. We then define that the schema must be dropped and created (drop-and-create
) everytime the application is run. In this example application, we are seeding the database everytime we run the app as we're going to use an in-memory database. This technique of dropping and creating a database is most likely, not suitable for production use !
The next two properties tell JPA how to create and drop the database table(s). In this case, to use the metadata defined for the Consteallation
class, rather than via user defined scripts.
Finally, the sql-load-script-source
tells JPA to run the script META-INF/import.sql
to seed the database. We'll look at this a bit later.
So, the persistence unit specifies that we are to use a JTA datasource called constellationDatasource
, but where is that defined ? In TomEE, datasources can be defined within a file called META-INF/resources.xml
In this instance, as below:
<Resource id="constellationDatasource" type="javax.sql.DataSource">
defaultAutoCommit = true
jdbcDriver = org.hsqldb.jdbcDriver
jdbcUrl = jdbc:hsqldb:mem:hsqldb
jtaManaged = true
maxActive = 20
password =
passwordCipher = PlainText
userName = sa
</Resource>
This code simply defines an in-memory hsql database that is accessed via a datasource called constellationDatasource
.
Populate Persistence Layer
We looked at this briefly before. Within the persistence unit, JPA allows us to specify a script that is run whenever the application starts up to seed the database with pre-defined data.
<property name="javax.persistence.sql-load-script-source"
value="META-INF/import.sql" />
To load the constellations data into the database, this file contains an INSERT statement for each Constellation, e.g.
insert into constellation(name, abbreviation, description) values ('Andromeda', 'And', 'The Chained Maiden');
insert into constellation(name, abbreviation, description) values ('Antila', 'Ant', 'The Air Pump');
etc.
Remember we don't have to create the database table as that is done by JPA, and we don't need to worry about duplicate entries in the database as JPA drops and creates the schema everytime the application (and the script) is executed.
Create JAX-RS Endpoint
We're nearly there !
In the previous sections, we've created a skeleton application, created a persistence layer and seeded the database with data. All that's left to do now is to develop a REST endpoint to query the data.
To use JPA, we need to define an Application
that specifies the base URI of the endpoint. The MicroProfile Starter did this for us automatically within the ConstellationapiRestApplication
class
package com.davidsalter.constellationapi;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("/api")
public class ConstellationapiRestApplication extends Application {
}
This class simply defines that the base URI of our endpoint will be /api
To implement an endpoint, we need to write a Resource that uses the DAO we created earlier to query the database. This is shown below:
package com.davidsalter.constellationapi.boundary;
import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import com.davidsalter.constellationapi.controller.ConstellationDao;
import com.davidsalter.constellationapi.entity.Constellation;
@Path("constellations")
@RequestScoped
public class ConstellationResource {
@Inject
private ConstellationDao constellationDao;
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Constellation> getConstellations() {
return constellationDao.findAllConstellations();
}
}
This class simply injects an instance of our ConstellationDao
, calls the method findAllConstellations
on it, and then returns the result as JSON to the caller.
OK. So we're there?
We've build an endpoint that will return the required data. We can build and execute the endpoint with:
$ mvn clean package tomee:run
and then test it with:
$ curl http://localhost/api/constellations
[{"abbreviation":"And","description":"The Chained Maiden","name":"Andromeda"},
etc...
All looks good so far. However, since our React website is running separately from TomEE, we'll need to configure CORS within TomEE to enable the React application to call the TomEE web services. We'll see how this can be done in Part 2.
Top comments (0)