§Integrating with JPA
§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.0.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 Java 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 thepersistence.xml
file will always stay inside the generated applicationjar
file.
This is a requirement by the JPA specification. According to it thepersistence.xml
file has to be in the samejar
file where its persistence-units’ entities live, otherwise these entities won’t be availabe for the persistence-units. (You could, however, explicitly add ajar
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 aFileNotFoundException
in development mode because there is nojar
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 applicationjar
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 play.db.jpa.JPAApi;
import javax.inject.*;
import javax.persistence.*;
import java.util.concurrent.*;
@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 Ebean ORM