Documentation

§Using JPA to access your database

Important Note:
Please be aware that JPA bean validation is currently not supported in Play 2.9. For further information, see details further below.

§Adding dependencies to your project

First you need to tell Play that your project depends on javaJpa which will provide JDBC and JPA api dependencies.

There is no built-in JPA implementation in Play; you can choose any available implementation. For example, to use Hibernate, just add the following dependency to your project:

libraryDependencies ++= Seq(
  javaJpa,
  "org.hibernate" % "hibernate-core" % "6.5.2.Final" // replace by your jpa implementation
)

§Exposing the datasource through JNDI

JPA requires the datasource to be accessible via JNDI. You can expose any Play-managed datasource via JNDI by adding this configuration in conf/application.conf:

db.default.jndiName=DefaultDS

See the Database docs for more information about how to configure your datasource.

§Creating a Persistence Unit

Next you have to create a proper persistence.xml JPA configuration file. Put it into the conf/META-INF directory, so it will be properly added to your classpath.

Here is a sample configuration file to use with Hibernate:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="https://jakarta.ee/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="https://jakarta.ee/xml/ns/persistence https://jakarta.ee/xml/ns/persistence/persistence_3_0.xsd"
             version="3.0">

    <persistence-unit name="defaultPersistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <non-jta-data-source>DefaultDS</non-jta-data-source>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
        </properties>
    </persistence-unit>

</persistence>

Finally you have to tell Play, which persistent unit should be used by your JPA provider. This is done by setting the jpa.default property in your conf/application.conf.

jpa.default=defaultPersistenceUnit

§Deploying Play with JPA

Running Play in development mode while using JPA will work fine, but in order to deploy the application you will need to add this to your build.sbt file.

PlayKeys.externalizeResourcesExcludes += baseDirectory.value / "conf" / "META-INF" / "persistence.xml"

Note: More information on how to configure externalized resources can be found here.
The above settings makes sure the persistence.xml file will always stay inside the generated application jar file.
This is a requirement by the JPA specification. According to it the persistence.xml file has to be in the same jar file where its persistence-units’ entities live, otherwise these entities won’t be available for the persistence-units. (You could, however, explicitly add a jar file containing entities via <jar-file>xxx.jar</jar-file> to a persistence-unit - but that doesn’t work well with Play as it would fail with a FileNotFoundException in development mode because there is no jar file that will be generated in that mode. Further that wouldn’t work well in production mode too because when deploying an application, the name of the generated application jar file changes with each new release as the current version of the application gets appended to it.)

§Using play.db.jpa.JPAApi

Play offers you a convenient API to work with Entity Manager and Transactions. This API is defined by play.db.jpa.JPAApi, which can be injected at other objects like the code below:

import jakarta.persistence.*;
import java.util.concurrent.*;
import javax.inject.*;
import play.db.jpa.JPAApi;

@Singleton
public class JPARepository {
  private JPAApi jpaApi;
  private DatabaseExecutionContext executionContext;

  @Inject
  public JPARepository(JPAApi api, DatabaseExecutionContext executionContext) {
    this.jpaApi = api;
    this.executionContext = executionContext;
  }
}

We recommend isolating your JPA operations behind a Repository or DAO, so that you can manage all your JPA operations with a custom execution context and transactions.

This means that all JPA operations are done behind the interface – JPA classes are package private, there is no exposure of persistence aware objects to the rest of the application, and sessions are not held open past the method that defines an asynchronous boundary (i.e. returns CompletionStage).

This may mean that your domain object (aggregate root, in DDD terms) has an internal reference to the repository and calls it to return lists of entities and value objects, rather than holding a session open and using JPA based lazy loading.

§Using a CustomExecutionContext

NOTE: Using JPA directly in an Action – which uses Play’s default rendering thread pool – will limit your ability to use Play asynchronously because JDBC blocks the thread it’s running on.

You should always use a custom execution context when using JPA, to ensure that Play’s rendering thread pool is completely focused on rendering pages and using cores to their full extent. You can use Play’s CustomExecutionContext class to configure a custom execution context dedicated to serving JDBC operations. See JavaAsync and ThreadPools for more details.

All the Play example templates on Play’s download page that use blocking APIs (i.e. Anorm, JPA) have been updated to use custom execution contexts where appropriate. For example, going to https://github.com/playframework/play-samples/tree/3.0.x/play-java-jpa-example shows that the JPAPersonRepository class takes a DatabaseExecutionContext that wraps all the database operations.

For thread pool sizing involving JDBC connection pools, you want a fixed thread pool size matching the connection pool, using a thread pool executor. Following the advice in HikariCP’s pool sizing page, you should configure your JDBC connection pool to double the number of physical cores, plus the number of disk spindles, i.e. if you have a four core CPU and one disk, you have a total of 9 JDBC connections in the pool:

# db connections = ((physical_core_count * 2) + effective_spindle_count)
fixedConnectionPool = 9

database.dispatcher {
  executor = "thread-pool-executor"
  throughput = 1
  thread-pool-executor {
    fixed-pool-size = ${fixedConnectionPool}
  }
}

§Running JPA transactions

The JPAApi provides you various withTransaction(...) methods to execute arbitrary code inside a JPA transaction. These methods however do not include a custom execution context and therefore must be wrapped inside a CompletableFuture with an IO bound execution context.

§Examples

Using JPAApi.withTransaction(Function<EntityManager, T>):

public CompletionStage<Long> runningWithTransaction() {
  return CompletableFuture.supplyAsync(
      () -> {
        // lambda is an instance of Function<EntityManager, Long>
        return jpaApi.withTransaction(
            entityManager -> {
              Query query = entityManager.createNativeQuery("select max(age) from people");
              return (Long) query.getSingleResult();
            });
      },
      executionContext);
}

Using JPAApi.withTransaction(Consumer<EntityManager>) to run a batch update:

public CompletionStage<Void> runningWithRunnable() {
  // lambda is an instance of Consumer<EntityManager>
  return CompletableFuture.runAsync(
      () -> {
        jpaApi.withTransaction(
            entityManager -> {
              Query query =
                  entityManager.createNativeQuery("update people set active = 1 where age > 18");
              query.executeUpdate();
            });
      },
      executionContext);
}

§Bean Validation Not Currently Supported in Play 2.9

The JPA specification defines a validation mode that enables entity validation. If this mode isn’t overridden by the user (e.g., <validation-mode>...</validation-mode> in the persistence.xml or via a property), it defaults to AUTO. This means that Hibernate automatically tries to detect the presence of a Bean Validation reference implementation (such as Hibernate Validator) on the classpath and, if found, automatically activates that bean validation. If no implementation is detected, no validation occurs. However, an exception is thrown if the mode was manually set to something other than NONE.

The challenge arises with Play and Hibernate ORM 6+. In this version, Hibernate ORM no longer detects Hibernate Validator version 6.x (which Play utilizes) on the classpath, as it continues to use javax.validation. Hibernate ORM 6+, however, only detects jakarta.validation classes.

While Hibernate Validator version 7.x has transitioned to Jakarta, upgrading to that version in Play is currently challenging. Play employs libraries in Play-Java-Forms to assist with binding, but only a newer version of these libraries has adopted Jakarta and supports Hibernate Validator 7. Unfortunately, these newer library versions are only compatible with Java 17. Since Play 2.9 continues to support Java 11, upgrading is not feasible. Attempting to manually override the hibernate-validator version to 7.x will result in a breakage of Play-Java-Form functionality.

If the absence of Bean Validation poses an issue for your use case, please inform us. This way, we can explore potential solutions to address this challenge.

§Enabling Play database evolutions

Read Evolutions to find out what Play database evolutions are useful for, and follow the setup instructions for using it.

Next: Using the Cache


Found an error in this documentation? The source code for this page can be found here. After reading the documentation guidelines, please feel free to contribute a pull request. Have questions or advice to share? Go to our community forums to start a conversation with the community.