Helidon MP JPA Guide

This guide shows how to configure and use the Java Persistence API (JPA) from within a Helidon MP application.

What You Need

For this 30 minute tutrial, you’ll need the following:

A Helidon {upper-case-flavor} ApplicationYou can use your own application or use the Helidon {upper-case-flavor} Quickstart to create a sample application.
Java SE 11 (Open JDK 11)Helidon requires Java 11+.
Maven 3.6.1+Helidon requires Maven 3.6.1+.
Docker 18.09+You need Docker if you want to build and deploy Docker containers.
Kubectl 1.16.5+If you want to deploy to Kubernetes, you need kubectl and a Kubernetes cluster (you can install one on your desktop).
curl(Optional) for testing
Verify Prerequisites
java -version
mvn --version
docker --version
kubectl version --short
Copied
Setting JAVA_HOME
# On Mac
export JAVA_HOME=`/usr/libexec/java_home -v 11`

# On Linux
# Use the appropriate path to your JDK
export JAVA_HOME=/usr/lib/jvm/jdk-11
Copied

Overview

By following this guide, you’ll enhance a bare-bones Helidon MP application to use JPA, with automatic transaction support, backed by EclipseLink, to access an in-memory H2 database. You’ll see how to install the relevant dependencies and add JPA-related code to your application.

Use the Maven Archetype to Generate a Helidon MP Application

In a shell, cd into an empty directory and run this:

mvn -U archetype:generate \
    -DinteractiveMode=false \
    -DarchetypeGroupId=io.helidon.archetypes \
    -DarchetypeArtifactId=helidon-bare-mp \
    -DarchetypeVersion=2.6.14 \
    -DgroupId=io.helidon.example \
    -DartifactId=helidon-jpa \
    -Dpackage=io.helidon.example.jpa
Copied

Now cd into helidon-jpa. The rest of this guide will assume all relative paths are relative to this directory.

Add the H2 Database Driver to the Runtime Classpath

Add the following dependency in your pom.xml:

pom.xml
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
Copied

In a production application, you may use a different database, so in that case you may add a different database driver dependency here instead.

Add the Hikari Connection Pool Extension to the Runtime Classpath

Add the following dependency in your pom.xml:

pom.xml
<dependency>
    <groupId>io.helidon.integrations.cdi</groupId>
    <artifactId>helidon-integrations-cdi-datasource-hikaricp</artifactId>
    <scope>runtime</scope>
</dependency>
Copied

Add the JTA Extension to the Runtime Classpath

Add the following dependency in your pom.xml:

pom.xml
<dependency>
    <groupId>io.helidon.integrations.cdi</groupId>
    <artifactId>helidon-integrations-cdi-jta-weld</artifactId>
    <scope>runtime</scope>
</dependency>
Copied

Add the Provider-Independent Helidon JPA Extension to the Runtime Classpath

Add the following dependency in your pom.xml:

pom.xml
<dependency>
    <groupId>io.helidon.integrations.cdi</groupId>
    <artifactId>helidon-integrations-cdi-jpa</artifactId>
    <scope>runtime</scope>
</dependency>
Copied

Add the following dependency in your pom.xml:

pom.xml
<dependency>
    <groupId>io.helidon.integrations.cdi</groupId>
    <artifactId>helidon-integrations-cdi-eclipselink</artifactId>
    <scope>runtime</scope>
</dependency>
Copied

Add the JTA and JPA Dependencies to the Provided Classpath

Add the following dependencies in your pom.xml:

pom.xml
<dependency>
    <groupId>jakarta.persistence</groupId>
    <artifactId>jakarta.persistence-api</artifactId>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>jakarta.transaction</groupId>
    <artifactId>jakarta.transaction-api</artifactId>
    <scope>compile</scope>
</dependency>
Copied

Add DDL to Create the Relevant Database Tables

Add the following file under src/main/resources:

src/main/resources/greeting.ddl
CREATE TABLE IF NOT EXISTS GREETING (
    SALUTATION VARCHAR(64) NOT NULL PRIMARY KEY,
    RESPONSE VARCHAR(64) NOT NULL
);

MERGE INTO GREETING (SALUTATION, RESPONSE) VALUES ('Marco', 'Polo');
Copied

Add an application.yaml File With Database Connectivity Information

Replace the contents of the following file under src/main/resources:

src/main/resources/application.yaml
server:
    port: 8080
javax:
    sql:
        DataSource:
            greetingDataSource:
                dataSourceClassName: org.h2.jdbcx.JdbcDataSource
                dataSource:
                    url: jdbc:h2:mem:greeting;INIT=RUNSCRIPT FROM 'classpath:greeting.ddl' 
                    user: sa
                    password: ""
Copied

Add a Java Class to Represent a Greeting JPA Entity

Add the following Java class under src/main/java/io/helidon/example/jpa:

src/main/java/io/helidon/example/jpa/Greeting.java
package io.helidon.example.jpa;

import java.io.Serializable;
import java.util.Objects;

import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Access(value = AccessType.FIELD) 
@Entity(name = "Greeting") 
@Table(name = "GREETING") 
public class Greeting implements Serializable { 

    @Column(
        insertable = true,
        name = "SALUTATION", 
        nullable = false,
        updatable = false
    )
    @Id 
    private String salutation;

    @Basic(optional = false) 
    @Column(
        insertable = true,
        name = "RESPONSE",
        nullable = false,
        updatable = true
    )
    private String response;

    @Deprecated
    protected Greeting() { 
        super();
    }

    public Greeting(String salutation, String response) { 
        super();
        this.salutation = Objects.requireNonNull(salutation);
        this.setResponse(response);
    }

    public String getSalutation() {
        return this.salutation;
    }

    public String getResponse() {
        return this.response;
    }

    public void setResponse(String response) {
        this.response = Objects.requireNonNull(response);
    }

    @Override
    public String toString() {
        return this.getSalutation() + " " + this.getResponse();
    }

}
Copied
  • (Some of the annotations in this example, like this one, have sensible defaults, but the example specifies them explicitly for clarity.) This Access annotation says that JPA will access this class' fields directly, rather than via getter and setter methods.
  • The Entity annotation identifies this class as a JPA entity. The name element value can be used in JPQL queries.
  • The Table annotation identifies the database table to which this class will be mapped.
  • JPA entities should be Serializable.
  • The Column annotation specifies what column in the database the annotated field maps to. The elements of the Column annotation further describe the column.
  • The Id annotation indicates this field will be mapped to the primary key of the database table.
  • The Basic annotation indicates this field will be mapped to an ordinary ("basic") column.
  • All JPA entities need a zero-argument constructor, but it doesn’t have to be public. This constructor satisfies this requirement. It is marked Deprecated and is non-public so that normal users have to supply data for the salutation and response fields via the other constructor.
  • This is the constructor normal users will use.

Add a META-INF/persistence.xml Descriptor

Add the following file under src/main/resources/META-INF:

src/main/resources/META-INF/persistence.xml
<?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="greeting" transaction-type="JTA"> 
        <description>A persistence unit for the greeting example.</description>
        <jta-data-source>greetingDataSource</jta-data-source> 
        <class>io.helidon.example.jpa.Greeting</class> 
        <properties> 
            <property name="eclipselink.deploy-on-startup" value="true"/>
            <property name="eclipselink.jdbc.native-sql" value="true"/>
            <property name="eclipselink.logging.logger" value="JavaLogger"/>
            <property name="eclipselink.logging.parameters" value="true"/>
            <property name="eclipselink.target-database" value="org.eclipse.persistence.platform.database.H2Platform"/> 
            <property name="eclipselink.target-server" value="io.helidon.integrations.cdi.eclipselink.CDISEPlatform"/> 
            <property name="eclipselink.weaving" value="false"/> 
        </properties>
    </persistence-unit>
</persistence>
Copied
  • Helidon MP’s JPA extension supports JPA 2.2.
  • Note that JTA is the transaction type. JTA transactions are fully supported.
  • Note that the name of the data source is the one configured in the application.yaml file described earlier.
  • The Greeting class you created is listed here.
  • The properties listed here are in general EclipseLink properties. Many are optional, but a few (detailed below) are required.
  • This property is required when EclipseLink is the JPA provider. It is set to org.eclipse.persistence.platform.database.H2Platform because this example uses the H2 database.
  • This property is required, and when EclipseLink is the JPA provider must have the value io.helidon.integrations.cdi.eclipselink.CDISEPlatform.
  • This property is required when EclipseLink is the JPA provider and must be set to false.

Modify the pom.xml File To Support Static Weaving

Weaving is the term that describes the bytecode manipulation that JPA providers perform upon your simple Java entity classes (like the Greeting class you created above). In Helidon MicroProfile’s JPA extension, weaving must be performed statically (at build time). Here we modify the pom.xml to make that happen.

Add the following plugin configuration in your pom.xml:

pom.xml
<plugin>
    <groupId>com.ethlo.persistence.tools</groupId>
    <artifactId>eclipselink-maven-plugin</artifactId>
    <executions>
        <execution>
            <id>weave</id>
            <phase>process-classes</phase>
            <goals>
                <goal>weave</goal> 
            </goals>
        </execution>
        <execution>
            <id>modelgen</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>modelgen</goal> 
            </goals>
        </execution>
    </executions>
</plugin>
Copied
  • Static weaving is performed on compiled classes in place.
  • The JPA static metamodel is generated by this goal.

Inject a Container-Managed EntityManager

In the src/main/java/io/helidon/example/jpa/GreetResource.java file, add the following imports:

src/main/java/io/helidon/example/jpa/GreetResource.java
import javax.enterprise.context.Dependent;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
Copied

Annotate the resource class declaration with @Dependent:

src/main/java/io/helidon/example/jpa/GreetResource.java
@Dependent 
public class GreetResource {
Copied
  • This ensures that io.helidon.example.jpa.GreetResource is a discoverable CDI bean, because it is an example of a bean-defining annotation.

Then add the following annotated field declaration:

src/main/java/io/helidon/example/jpa/GreetResource.java
@PersistenceContext 
private EntityManager em;
Copied

Use the Injected EntityManager

In the src/main/java/io/helidon/example/jpa/GreetResource.java file, add the following import:

src/main/java/io/helidon/example/jpa/GreetResource.java
import javax.transaction.Transactional;
import javax.ws.rs.PathParam;
Copied

Add the following resource method to the GreetResource class:

src/main/java/io/helidon/example/jpa/GreetResource.java
@GET
@Path("response/{salutation}")
@Produces("text/plain")
@Transactional 
public String getResponse(@PathParam("salutation") String salutation) {
    final Greeting greeting = this.em.find(Greeting.class, salutation);
    final String returnValue;
    if (greeting == null) {
        returnValue = null;
    } else {
        returnValue = greeting.getResponse();
    }
    return returnValue;
}
Copied
  • A JTA transaction will be automatically started at the beginning of this method when it is invoked as a result of an incoming HTTP request, and committed or rolled back when the method terminates normally or exceptionally. The injected EntityManager will join the transaction automatically.

Add Logging

Add the following content to the logging.properties file under src/main/resources:

src/main/resources/logging.properties
com.zaxxer.hikari.level=INFO
h2database.level=WARNING
io.netty.level=INFO
org.eclipse.persistence.level=FINE
org.glassfish.jersey.server.level=CONFIG
Copied

Build the Application

Execute the following from the root directory of your application:

mvn package
Copied

Run the Application

Execute the following from the root directory of your application:

java -jar target/helidon-jpa.jar
Copied

Test the Application

Execute the following:

curl http://localhost:8080/greet/response/Marco
Copied

Observe that Polo is returned.