Documentation

You are viewing the documentation for the 2.8.21 release in the 2.8.x series of releases. The latest stable release series is 3.0.x.

§Using JPA to access your database

§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" % "5.4.33.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:

<persistence 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_1.xsd"
             version="2.1">

    <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 availabe 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 java.util.concurrent.*;
import javax.inject.*;
import javax.persistence.*;
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 of 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-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);
}

§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