Helidon MP with JPA (with Hibernate) — A step-by-step tutorial

Hareesh Gopidas
6 min readJul 16, 2020

--

Now that you are here , I assume you would be knowing that Helidon is a micro-service framework released by Oracle . As of now the framework supports two programming models namely Helidon MP and Helidon SE .

Comparison between helidon SE and helidon MP

In this tutorial we are primarily going to look at how to create a Helidon MP microservice with JPA support .

Helidon MP is an implementation of Microprofile — Java EE’s answer to microservices platform— which is primarily contributed by Eclipse and hence mostly known as Eclipse Microprofile .

Read more about Helidon here https://helidon.io/#/

Let’s look at how to create a project so that we can jump into this new interesting framework .

Step 1 — Create a quickstart Helidon application with maven archetype

Use a maven archetype to create the project skeleton. Helidon has released a set of archetypes to make life easy for us . Look at the available quickstart maven archetypes from Helidon here — https://mvnrepository.com/artifact/io.helidon.archetypes

In our case we are going to set up a project with the archetype helidon-quickstart-mp

mvn archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-quickstart-mp \
-DarchetypeVersion=2.0.1 \
-DgroupId=com.hkg.helidon \
-DartifactId=jpa-helidon-sample \
-Dpackage=com.hkg.helidon.sample \
-DapplicationName=HelidonJPAApp

The archetype will generate a quickstart helidon mp project with the necessary code .

The generated code will have a resource named GreetResource as a quickstart API .

Build the project and run the application as a jar .

java -jar target/jpa-helidon-sample.jar

Once application is started you can access the Greet resource like given below

curl GET http://localhost:8080/greet

And we will see the below JSON response

{"message":"Hello World!"}

Step 2 — Add dependencies in pom.xml for JPA support

Add the CDI ( context and dependency injection ) dependencies for JPA .

<dependency>
<groupId>io.helidon.integrations.cdi</groupId>
<artifactId>helidon-integrations-cdi-jpa</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.cdi</groupId>
<artifactId>helidon-integrations-cdi-jta-weld</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.integrations.cdi</groupId>
<artifactId>helidon-integrations-cdi-datasource-hikaricp</artifactId>
</dependency>

Helidon provides out-of-the-box support for JPA implementations like EclipseLink and Hibernate . Most of the examples that are available around the internet are using EclipseLink , so in this tutorial I am going to go with Hibernate .

Let’s add the dependency for the helidon Hibernate support .

<dependency>
<groupId>io.helidon.integrations.cdi</groupId>
<artifactId>helidon-integrations-cdi-hibernate</artifactId>
<version>2.0.0</version>
</dependency>

Next add the dependency for the database drivers . Here we are going to use PostgreSQL , since we will be running a postgresql database using Docker .

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.7</version>
</dependency>

And finally we add the jakarta JPA and JTA libraries to support the CDI libraries we added earlier

<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
</dependency>

That’s all with the pom.xml !

Step -3 Let’s set up a PostgreSQL database ( you can skip this step if you already have a database up and running )

We are going to set up a docker-compose file for starting up a docker container of PostgreSQL .

Create the docker-compose.yml file in the project root and define the details as given below

version: '3'
services:
airport-db:
image: library/postgres:latest
ports:
- "5432:5432"
container_name: aiport-db-postgres
environment:
- POSTGRES_DB= airport-db
- POSTGRES_PASSWORD=docker
- POSTGRES_USER=docker
volumes:
- ./data:/var/lib/postgresql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql

As you can see we have included an init.sql file which has the database script to create a database objects and to initialise the data.

contents of init.sql is as given below

CREATE TABLE public.airport (
id bigint NOT NULL,
apt_code character varying(255),
apt_name text,
city_name text,
country text
);
CREATE SEQUENCE public.apt_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
INSERT INTO public.airport VALUES (1, 'DXB', 'Dubai International Airport', 'Dubai', 'UAE');
INSERT INTO public.airport VALUES (2, 'JFK', 'John F Kennedy International Airport', 'Newyork', 'USA');
INSERT INTO public.airport VALUES (3, 'COK', 'Cochin International Airport', 'Kochi', 'India');
SELECT pg_catalog.setval('apt_id_seq', 3, true);

To start the data base use the docker compose up command

docker-compose up 

Step 4 : Configuring Helidon to connect to the Database

Create persistence.xml configured for postgreSQL under the folder src/resources/META-INF

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
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">
<persistence-unit name="aptrestPU" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>airportDataSource</jta-data-source>


<properties>

<property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/airport-db"/>
<property name="javax.persistence.jdbc.user" value="docker" />
<property name="javax.persistence.jdbc.password" value="docker" />
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect" />
<property name="hibernate.archive.autodetection" value="class" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="update" />

</properties>
</persistence-unit>
</persistence>

Now we need to configure the Datasource in the application.yaml so that a default datasource is injected to the EntityManager .

Content of the application.yaml is given below

server:
port: 8080
javax:
sql:
DataSource:
airportDataSource:
dataSourceClassName: org.postgresql.ds.PGSimpleDataSource
dataSource:
url: jdbc:postgresql://localhost:5432/airport-db
user: docker
password: docker

Step -5 : Implementing an end-to-end JAX-RS based CRUD service

Create the following classes

Airport.java

package com.hkg.helidon.airport.enitity;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.SequenceGenerator;
import javax.persistence.Table;
@Entity
@Table(name = "AIRPORT")
public class Airport implements Serializable {

/**
*
*/
private static final long serialVersionUID = 1L;
@Id
@Column(name="id")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "apt_id_seq")
@SequenceGenerator(name = "apt_id_seq", sequenceName = "apt_id_seq", allocationSize=1)
private Long id ;

@Column(name="apt_code")
private String airportCode;

@Column(name="apt_name")
private String airportName;

@Column(name="city_name")
private String cityName;

@Column(name="country")
private String countryName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAirportCode() {
return airportCode;
}
public void setAirportCode(String airportCode) {
this.airportCode = airportCode;
}
public String getAirportName() {
return airportName;
}
public void setAirportName(String airportName) {
this.airportName = airportName;
}
public String getCityName() {
return cityName;
}
public void setCityName(String cityName) {
this.cityName = cityName;
}
public String getCountryName() {
return countryName;
}
public void setCountryName(String countryName) {
this.countryName = countryName;
}
}

AirportRespository.java

package com.hkg.helidon.airport.repository;import java.util.List;import javax.enterprise.context.ApplicationScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaDelete;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
import com.hkg.helidon.airport.enitity.Airport;@ApplicationScoped
public class AirportRepository {
@PersistenceContext
EntityManager entityManager;
@Transactional
public Airport createOrUpdate(Airport airport) {
if (airport.getId() == null) {
this.entityManager.persist(airport);
return airport;
} else {
return this.entityManager.merge(airport);
}
}
@Transactional
public void deleteById(Long id) {
CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
CriteriaDelete<Airport> delete = cb.createCriteriaDelete(Airport.class);
Root<Airport> root = delete.from(Airport.class);
delete.where(cb.equal(root.get("id"), id));
this.entityManager.createQuery(delete).executeUpdate();
}
public List<Airport> getAllAirports() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Airport> cq = cb.createQuery(Airport.class);
Root<Airport> rootEntry = cq.from(Airport.class);
CriteriaQuery<Airport> all = cq.select(rootEntry);
TypedQuery<Airport> allQuery = entityManager.createQuery(all);
return allQuery.getResultList();
}

}

AirportService.java

package com.hkg.helidon.airport.service;import java.util.List;import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.transaction.Transactional;
import com.hkg.helidon.airport.enitity.Airport;
import com.hkg.helidon.airport.repository.AirportRepository;
@ApplicationScoped
public class AirportService {

private final AirportRepository airportRepository;

@Inject
public AirportService(AirportRepository airportRepository) {
this.airportRepository = airportRepository;
}

@Transactional
public Airport save(Airport airport) {
return airportRepository.createOrUpdate(airport);
}

public List<Airport> getAllAirports() {
return airportRepository.getAllAirports();
}

@Transactional
public void deleteByid(Long id) {
airportRepository.deleteById(id);
}
}

AirportResource.java

package com.hkg.helidon.airport.resource;import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.MediaType;
import static javax.ws.rs.core.Response.ok;
import com.hkg.helidon.airport.enitity.Airport;
import com.hkg.helidon.airport.service.AirportService;
@Path("/airport")
@RequestScoped
public class AirportResource {
private final AirportService airporService;@Context
UriInfo uriInfo;
@Context
ResourceContext resourceContext;
@Inject
public AirportResource(AirportService airporService) {
this.airporService = airporService;
}
@GET
@Path("/all")
@Produces(MediaType.APPLICATION_JSON)
public Response getAllAirports() {
return ok(this.airporService.getAllAirports()).build();
}
@POST
@Path("/save")
@Consumes(MediaType.APPLICATION_JSON)
public Response save(Airport airport) {
return ok(this.airporService.save(airport)).build();
}
@DELETE
@Path("/delete/{id}/")
public Response deleteOrderById(@PathParam("id") Long id) {
try {
this.airporService.deleteByid(id);
} catch (Exception e) {
return Response.status(Response.Status.OK).entity("Delete failed").build();
}
return Response.status(Response.Status.OK).entity("Deleted successfully").build();
}
}

Step-6 Add the entity in persistence-unit

Add the Airport entry in persistence.xml under the persistence-unit

<persistence-unit name="aptrestPU" transaction-type="JTA">
.....
<jta-data-source>airportDataSource</jta-data-source><class>com.hkg.helidon.airport.enitity.Airport</class>
<properties>
......
......
</persistence-unit>

Step-7 The final step . Build , Run and Test your project

Build the project with maven clean install

And now you can run the application

java -jar target/jpa-helidon-sample.jar

Test the application for the GET,POST and DELETE functions

curl GET http://localhost:8080/airport/all

To save a new entry

curl POST http://localhost:8080/airport/save {"airportCode":"AMS","airportName":"Amsterdam International Airport","cityName":"Amsterdam","countryName":"Netherlands"}

To save a delete an entry

curl DELETE http://localhost:8080/airport/delete/4

So we saw how to configure a helidon MP microservice with JPA support and build and end-to-end CRUD service.

As you can see HelidonMP is not yet a full fledged framework like Springboot and we had to spend time on the initial configuration which gets done in spring-boot initializer out of the box. May be down the line the support will be added in Helidon .

You can download the code from my github repository .https://github.com/hareeshkg/jpa-helidon-sample

--

--