The Java EE platform is developed through the Java Community Process (JCP ),
which is responsible for all Java technologies. Expert groups composed of interested parties have created
Java Specification Requests (JSRs) to define the various Java EE technologies. The work of the Java Community under the
JCP program helps to ensure Java technology’s standards of stability and cross-platform compatibility.
Introduction
In this post we are going to learn how to use Spring Data
in a Java EE environment using JAX-RS.
Project Structure
At the end of this guide our folder structure will look similar to the following:
.
|__src/
| |__main/
| | |__java/
| | | |__com/
| | | | |__juliuskrah
| | | | | |__ApplicationConfig.java
| | | | | |__LocalDateAdapter.java
| | | | | |__LocalDateTimeAdapter.java
| | | | | |__package-info.java
| | | | | |__Person.java
| | | | | |__PersonRepository.java
| | | | | |__PersonService.java
| | |__resources/
| | | |__META-INF/
| | | | |__persistence.xml
|__pom.xml
You can also find the complete sample in the github repository .
Prerequisites
To follow along this guide, your development system should have the following setup:
Generate
a maven project template and let us begin.
Project Dependencies
To get started, we need to gather our project dependencies. This is a maven based project, and we can easily declare
our dependencies in a pom.xml
file.
file:
pom.xml
...
<properties>
<project.build.sourceEncoding> UTF-8</project.build.sourceEncoding>
<maven.compiler.source> 1.8</maven.compiler.source>
<maven.compiler.target> 1.8</maven.compiler.target>
<h2.version> 1.4.196</h2.version>
<hibernate.version> 5.2.10.Final</hibernate.version>
<javaee-api.version> 7.0</javaee-api.version>
<spring-data-releasetrain.version> Ingalls-SR6</spring-data-releasetrain.version>
<wildfly-swarm.version> 2017.8.1</wildfly-swarm.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId> org.springframework.data</groupId>
<artifactId> spring-data-releasetrain</artifactId>
<version> ${spring-data-releasetrain.version}</version>
<type> pom</type>
<scope> import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId> org.springframework.data</groupId>
<artifactId> spring-data-jpa</artifactId>
</dependency>
<dependency>
<groupId> org.hibernate</groupId>
<artifactId> hibernate-core</artifactId>
<version> ${hibernate.version}</version>
<scope> provided</scope>
</dependency>
<!-- Java EE 7 dependency -->
<dependency>
<groupId> javax</groupId>
<artifactId> javaee-api</artifactId>
<version> ${javaee-api.version}</version>
<scope> provided</scope>
</dependency>
<dependency>
<groupId> com.h2database</groupId>
<artifactId> h2</artifactId>
<version> ${h2.version}</version>
</dependency>
</dependencies>
<build>
<finalName> ${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId> org.apache.maven.plugins</groupId>
<artifactId> maven-war-plugin</artifactId>
<version> 3.1.0</version>
<configuration>
<failOnMissingWebXml> false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId> org.wildfly.swarm</groupId>
<artifactId> wildfly-swarm-plugin</artifactId>
<version> ${wildfly-swarm.version}</version>
<configuration>
<properties>
<swarm.context.path> ${project.build.finalName}</swarm.context.path>
</properties>
</configuration>
<executions>
<execution>
<goals>
<goal> package</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
We have declared dependencies on Spring-Data
, Hibernate
and Java EE 7
. H2
embedded database has also been added
for JPA
access. In the plugins section we have wildfly-Swarm
which will take care of setting up a Java EE environment for this
project.
JPA Setup
Spring Data JPA as the name implies uses JPA , and for this reason we are going to setup JPA:
file:
src/main/resources/META-INF/persistence.xml
<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= "javaee-spring-dataPU" transaction-type= "JTA" >
<properties>
<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= "hibernate.show_sql" value= "true" />
</properties>
</persistence-unit>
</persistence>
Next is to create an Entity class to be managed by the Entity Manager of the Java EE Container:
file:
src/main/java/com/juliuskrah/Person.java
@Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L ;
@Id
@GeneratedValue ( generator = "uuid2" )
@GenericGenerator ( name = "uuid2" , strategy = "uuid2" )
private String id ;
private String firstName ;
private String lastName ;
private LocalDate dateOfBirth ;
private LocalDateTime createdDate ;
private LocalDateTime modifiedDate ;
// Getters and Setters omitted for brevity
}
Spring Data JPA and JAX-RS
With JPA setup (albeit simply), we can now start using Spring Data:
file:
src/main/java/com/juliuskrah/PersonRepository.java
public interface PersonRepository extends JpaRepository < Person , String > {
// Nothing here
}
Create the resource class to respond to HTTP requests over REST
and access Spring Data Repository abstraction:
file:
src/main/java/com/juliuskrah/PersonService.java
// The @Stateless annotation eliminates the need for manual transaction demarcation
@Stateless
@Path ( "/v1.0/persons" )
public class PersonService {
private static final Logger LOGGER = Logger . getLogger ( PersonService . class . getName ());
@PersistenceContext
private EntityManager entityManager ;
private PersonRepository personRepository ;
@PostConstruct
private void init () {
// Instantiate Spring Data factory
RepositoryFactorySupport factory = new JpaRepositoryFactory ( entityManager );
// Get an implemetation of PersonRepository from factory
this . personRepository = factory . getRepository ( PersonRepository . class );
}
/**
* POST /api/v1.0/persons : Create a new person.
*
* @param person the person to create
* @return the Response with status 201 (Created) and with location of newly created resource
* or with status 400 (Bad Request) if the person has
* already an ID
*/
@POST
public Response createPerson ( Person person ) {
LOGGER . log ( Level . INFO , "REST request to create Person : {0}" , person );
if ( Objects . nonNull ( person . getId ()))
throw new WebApplicationException ( Response . Status . BAD_REQUEST );
person . setCreatedDate ( LocalDateTime . now ());
this . personRepository . save ( person );
return Response . created ( URI . create ( "/v1.0/persons/" + person . getId ())). build ();
}
/**
* GET /api/v1.0/persons : get all the people.
*
* @return the list of people with status 200 (OK)
*/
@GET
public List < Person > findPeople () {
LOGGER . log ( Level . INFO , "REST request to fetch all Persons" );
return this . personRepository . findAll ();
}
/**
* GET /api/v1.0/persons/:id : get the "id" person.
*
* @param id the id of the person to retrieve
* @return the Response with status 200 (OK) and with body the person,
* or with status 404 (Not Found)
*/
@GET
@Path ( "{id: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}" )
public Response findPerson ( @PathParam ( "id" ) String id ) {
LOGGER . log ( Level . INFO , "REST request to fetch Person : {0}" , id );
Person person = this . personRepository . findOne ( id );
if ( Objects . isNull ( person ))
throw new WebApplicationException ( Response . Status . NOT_FOUND );
return Response . ok ( person ). build ();
}
/**
* PUT /api/v1.0/persons/:id : Updates an existing person.
*
* @param id the id of the person to retrieve
* @param person the person to update
* @return the Response with status 204 (NO CONTENT) and with no body,
* or with status 400 (Bad Request) if the person is not
* valid, or with status 500 (Internal Server Error) if the person
* couldn't be updated
*/
@PUT
@Path ( "{id: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}" )
public Response updatePerson ( @PathParam ( "id" ) String id , Person person ) {
LOGGER . log ( Level . INFO , "REST request to update Person : {0}" , person );
Person person1 = this . personRepository . findOne ( id );
if ( Objects . isNull ( person1 ))
throw new WebApplicationException ( Response . Status . NOT_FOUND );
person . setId ( id );
person . setCreatedDate ( person1 . getCreatedDate ());
person . setModifiedDate ( LocalDateTime . now ());
this . personRepository . save ( person );
return Response . noContent (). build ();
}
/**
* DELETE /api/v1.0/persons/:id : delete the "id" person.
*
* @param id the id of the person to delete
* @return the Response with status 204 (NO CONTENT)
*/
@DELETE
@Path ( "{id: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}}" )
public Response deletePerson ( @PathParam ( "id" ) String id ) {
LOGGER . log ( Level . INFO , "REST request to delete Person : {0}" , id );
this . personRepository . delete ( id );
return Response . noContent (). build ();
}
}
Finally let us activate JAX-RS with:
file:
src/main/java/com/juliuskrah/ApplicationConfig.java
/**
* A class extending {@link Application} and annotated with @ApplicationPath is the Java EE 7 "no XML" approach to activating
* JAX-RS.
* <p>
* <p>
* Resources are served relative to the servlet path specified in the {@link ApplicationPath} annotation.
* </p>
*/
@ApplicationPath ( "api" )
public class ApplicationConfig extends Application {
/* class body intentionally left blank */
}
We are using Java 8 data-types of LocalDateTime
and LocalDate
and the Jackson
JSON serializer/deserializer in use needs to know how
marshal/unmarshal these types. One way to do this is add the jackson-datatype-jsr310
jar to the classpath and register the module
with the ObjectMapper
.
In our situation we only need to serialize and deserialize two data-types which we can wire manually instead of adding another dependency:
file:
src/main/java/com/juliuskrah/LocalDateTimeAdapter.java
public class LocalDateTimeAdapter extends XmlAdapter < String , LocalDateTime > {
@Override
public LocalDateTime unmarshal ( String dateInput ) {
return LocalDateTime . parse ( dateInput , DateTimeFormatter . ISO_DATE_TIME );
}
@Override
public String marshal ( LocalDateTime localDateTime ) {
return DateTimeFormatter . ISO_DATE_TIME . format ( localDateTime );
}
}
file:
src/main/java/com/juliuskrah/LocalDateAdapter.java
public class LocalDateAdapter extends XmlAdapter < String , LocalDate > {
@Override
public LocalDate unmarshal ( String dateInput ) {
return LocalDate . parse ( dateInput , DateTimeFormatter . ISO_DATE );
}
@Override
public String marshal ( LocalDate localDate ) {
return DateTimeFormatter . ISO_DATE . format ( localDate );
}
}
Then we register it to be used for all LocalDate
and LocalDateTime
instances in the application.
file:
src/main/java/com/juliuskrah/package-info.java
@XmlJavaTypeAdapters ({
@XmlJavaTypeAdapter ( type = LocalDateTime . class ,
value = LocalDateTimeAdapter . class ),
@XmlJavaTypeAdapter ( type = LocalDate . class ,
value = LocalDateAdapter . class )
})
package com.juliuskrah ;
Testing the Application
Package the application with wildfly-swarm by running:
> mvn clean package
This will create a jar file: ${project.build.finalName}-swarm.jar
in the target
directory.
Run the following command to start an Undertow HTTP Server
on port 8080
and on context path
/${project.artifactId}
(in my case /javaee-spring-data
).
> java -jar target\j avaee-spring-data-swarm.jar # Windows
> java -jar target/* .jar # Linux and Mac
Open another shell window and test the application:
> curl -i -X POST -H "Content-Type: application/json" -d "{ \" firstName \" : \" John \" , \" lastName \" : \" Doe \" , \" dateOfBirth \" : \" 2017-08-19 \" }" http://localhost:8080/javaee-spring-data/api/v1.0/persons
HTTP/1.1 201 Created
Connection: keep-alive
Location: http://localhost:8080/javaee-spring-data/api/v1.0/persons/ebd64f47-73ec-4829-a79d-32b8c9c83186
Content-Length: 0
Date: Sun, 20 Aug 2017 19:14:48 GMT
Access the newly created person:
> curl -i -X GET -H "Accept: application/json" http://localhost:8080/javaee-spring-data/api/v1.0/persons/ebd64f47-73ec-4829-a79d-32b8c9c83186
HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: application/json
Content-Length: 168
Date: Sun, 20 Aug 2017 19:21:57 GMT
{
"id" :"ebd64f47-73ec-4829-a79d-32b8c9c83186" ,
"firstName" : "John" ,
"lastName" : "Doe" ,
"dateOfBirth" : "2017-08-19" ,
"createdDate" : "2017-08-20T19:14:48.581" ,
"modifiedDate" : null
}
Update the person:
> curl -i -X PUT -H "Content-Type: application/json" -d "{ \" firstName \" : \" Jane \" , \" lastName \" : \" Doe \" , \" dateOfBirth \" : \" 2017-08-19 \" }" http://localhost:8080/javaee-spring-data/api/v1.0/persons/ebd64f47-73ec-4829-a79d-32b8c9c83186
HTTP/1.1 204 No Content
Date: Sun, 20 Aug 2017 19:31:55 GMT
Delete the person:
> curl -i -X DELETE http://localhost:8080/javaee-spring-data/api/v1.0/persons/ebd64f47-73ec-4829-a79d-32b8c9c83186
HTTP/1.1 204 No Content
Date: Sun, 20 Aug 2017 19:34:59 GMT
Conclusion
In this post we learned how to integrate Spring Data in a Java EE application using wildfly-Swarm as EE container.
You can find the source to this guide in the github repository . Until the next post, keep doing cool things .
If you would like to support this blog consider