- 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} Application | You 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 |
java -version
mvn --version
docker --version
kubectl version --short# 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-11Overview
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.
This guide assumes that you have read the following: An understanding of named data source support in Helidon MP and An understanding of transaction support in Helidon MP.
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.jpaNow 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>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>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>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>Add the EclipseLink 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-eclipselink</artifactId>
<scope>runtime</scope>
</dependency>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>Add DDL to Create the Relevant Database Tables
Add the following file under src/main/resources:
src/main/resources/greeting.ddlCREATE 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');Add an application.yaml File With Database Connectivity Information
Replace the contents of the following file under src/main/resources:
src/main/resources/application.yamlserver:
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: ""- The H2
INITproperty tells H2 what command to run upon starting up. In this case, it is going to load and run the DDL mentioned above.
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.javapackage 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();
}
}- (Some of the annotations in this example, like this one, have sensible defaults, but the example specifies them explicitly for clarity.) This
Accessannotation says that JPA will access this class' fields directly, rather than via getter and setter methods. - The
Entityannotation identifies this class as a JPA entity. Thenameelement value can be used in JPQL queries. - The
Tableannotation identifies the database table to which this class will be mapped. - JPA entities should be
Serializable. - The
Columnannotation specifies what column in the database the annotated field maps to. The elements of theColumnannotation further describe the column. - The
Idannotation indicates this field will be mapped to the primary key of the database table. - The
Basicannotation 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 markedDeprecatedand is non-publicso that normal users have to supply data for thesalutationandresponsefields 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>- Helidon MP’s JPA extension supports JPA 2.2.
- Note that
JTAis the transaction type. JTA transactions are fully supported. - Note that the name of the data source is the one configured in the
application.yamlfile described earlier. - The
Greetingclass 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.H2Platformbecause 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>- 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.javaimport javax.enterprise.context.Dependent;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;Annotate the resource class declaration with @Dependent:
src/main/java/io/helidon/example/jpa/GreetResource.java@Dependent
public class GreetResource {- This ensures that
io.helidon.example.jpa.GreetResourceis 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;- The
@PersistenceContextannotation indicates that you want anEntityManagerinjected here.
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.javaimport javax.transaction.Transactional;
import javax.ws.rs.PathParam;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;
}- 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
EntityManagerwill join the transaction automatically.
Add Logging
Add the following content to the logging.properties file under src/main/resources:
src/main/resources/logging.propertiescom.zaxxer.hikari.level=INFO
h2database.level=WARNING
io.netty.level=INFO
org.eclipse.persistence.level=FINE
org.glassfish.jersey.server.level=CONFIGBuild the Application
Execute the following from the root directory of your application:
mvn packageRun the Application
Execute the following from the root directory of your application:
java -jar target/helidon-jpa.jarTest the Application
Execute the following:
curl http://localhost:8080/greet/response/MarcoObserve that Polo is returned.