- Using JPA in Helidon MP
This guide shows how to configure and use the Java Persistence API (JPA) from within a Helidon MP application.
What You Need
| About 30 minutes |
| Helidon Prerequisites |
| An understanding of named data source support in Helidon MP |
| An understanding of transaction support in Helidon MP |
| An understanding of JPA itself |
What You’ll Do
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 archetype:generate \
-DinteractiveMode=false \
-DarchetypeGroupId=io.helidon.archetypes \
-DarchetypeArtifactId=helidon-mp \
-DarchetypeVersion=1.4.12 \
-DgroupId=io.helidon.example \
-DartifactId=helidon-jpa \
-Dpackage=io.helidon.example.jpa \
-DrestResourceName=ExampleResource \
-DapplicationName=ExampleApplicationNow 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>
<version>1.4.199</version>
<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>
<version>2.2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<version>1.2</version>
<scope>provided</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>
<version>2.7.1.1</version>
<dependencies>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
<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>- This plugin requires this
<dependencies>section as a workaround. - 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/ExampleResource.java file, add the following imports:
src/main/java/io/helidon/example/jpa/ExampleResource.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/ExampleResource.java@Dependent
public class ExampleResource {- This ensures that
io.helidon.example.jpa.ExampleResourceis a discoverable CDI bean.
Then add the following annotated field declaration:
src/main/java/io/helidon/example/jpa/ExampleResource.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/ExampleResource.java file, add the following import:
src/main/java/io/helidon/example/jpa/ExampleResource.javaimport javax.transaction.Transactional;
import javax.ws.rs.PathParam;Add the following resource method to the ExampleResource class:
src/main/java/io/helidon/example/jpa/ExampleResource.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 file under src/main/resources:
src/main/resources/logging.properties.level=INFO
handlers=io.helidon.common.HelidonConsoleHandler
java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n
com.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/example/response/MarcoObserve that Polo is returned.