JPA, Step-by-Step
This guide details step-by-step how to configure JPA in SeedStack, to access an existing relational database.
Step 1: The dependencies
JPA add-on
First, add the SeedStack JPA add-on:
<dependency>
<groupId>org.seedstack.addons.jpa</groupId>
<artifactId>jpa</artifactId>
</dependency>
Show version
dependencies {
compile("org.seedstack.addons.jpa:jpa:4.2.0")
}
JPA provider
Beyond the JPA add-on, an actual JPA implementation, called a JPA provider, is required:
Hibernate is a very popular JPA implementation. When using Hibernate, SeedStack is able to stream results from the database without putting them all in memory (useful when retrieving for result sets).
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("org.hibernate:hibernate-entitymanager:LATEST_VERSION_HERE")
}
Eclipse link is a also popular and is the reference JPA implementation.
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("org.eclipse.persistence:eclipselink:LATEST_VERSION_HERE")
}
Connection pooling
If you want connection pooling, you have to add the dependency for one of the following pooling libraries:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("com.zaxxer:HikariCP:LATEST_VERSION_HERE")
}
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("com.mchange:c3p0:LATEST_VERSION_HERE")
}
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("org.apache.commons:commons-dbcp2:LATEST_VERSION_HERE")
}
JDBC driver
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("com.h2database:h2:LATEST_VERSION_HERE")
}
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("org.hsqldb:hsqldb:LATEST_VERSION_HERE")
}
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("org.postgresql:postgresql:LATEST_VERSION_HERE")
}
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("org.mariadb.jdbc:mariadb-java-client:LATEST_VERSION_HERE")
}
<dependency>
<groupId>com.oracle.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>LATEST_VERSION_HERE</version>
</dependency>
dependencies {
compile("com.oracle.jdbc:ojdbc8:LATEST_VERSION_HERE")
}
The JDBC driver can be obtained from the directory of your DB2 installation.
Step 2: Datasource configuration
JPA relies on a JDBC datasource to access the database. We are now going to configure such datasource through the SeedStack JDBC add-on (pulled transitively by the JPA add-on):
jdbc:
datasources:
myDatasource:
provider: org.seedstack.jdbc.internal.datasource.HikariDataSourceProvider
url: jdbc:h2:mem:mydb
jdbc:
datasources:
myDatasource:
provider: org.seedstack.jdbc.internal.datasource.HikariDataSourceProvider
url: jdbc:hsqldb:mem:mydb
jdbc:
datasources:
myDatasource:
provider: org.seedstack.jdbc.internal.datasource.HikariDataSourceProvider
url: jdbc:postgresql://localhost:5740/mydb
jdbc:
datasources:
myDatasource:
provider: org.seedstack.jdbc.internal.datasource.HikariDataSourceProvider
url: jdbc:mariadb://localhost:3306/mydb
jdbc:
datasources:
myDatasource:
provider: org.seedstack.jdbc.internal.datasource.HikariDataSourceProvider
url: jdbc:oracle:thin:@localhost:1521:mydb
jdbc:
datasources:
myDatasource:
provider: org.seedstack.jdbc.internal.datasource.HikariDataSourceProvider
url: jdbc:db2://localhost:5021/mydb
Step 3: Unit configuration
JPA organizes persisted classes in buckets called «JPA units». All classes persisted within a JPA unit share a common datasource and configuration. A local transaction is bound to a JPA unit (it cannot span over multiple units). We are now going to configure a JPA unit that uses the datasource we specified before:
jpa:
units:
myUnit:
datasource: myDatasource
properties:
hibernate.dialect: org.hibernate.dialect.H2Dialect
hibernate.hbm2ddl.auto: update
jpa:
units:
myUnit:
datasource: myDatasource
properties:
hibernate.dialect: org.hibernate.dialect.HSQLDialect
hibernate.hbm2ddl.auto: update
jpa:
units:
myUnit:
datasource: myDatasource
properties:
hibernate.dialect: org.hibernate.dialect.PostgreSQLDialect
hibernate.hbm2ddl.auto: update
jpa:
units:
myUnit:
datasource: myDatasource
properties:
hibernate.dialect: org.hibernate.dialect.MariaDB53Dialect
hibernate.hbm2ddl.auto: update
jpa:
units:
myUnit:
datasource: myDatasource
properties:
hibernate.dialect: org.hibernate.dialect.Oracle12cDialect
hibernate.hbm2ddl.auto: update
jpa:
units:
myUnit:
datasource: datasource1
properties:
hibernate.dialect: org.hibernate.dialect.DB2Dialect
hibernate.hbm2ddl.auto: update
Step 4: Classes configuration
To automatically configure the JPA unit, SeedStack automatically detected persisted class (i.e. JPA entities). However it needs additional information to associate a particular class to a particular unit. This is done with class configuration:
classes:
org:
generated:
project:
domain:
model:
jpaUnit: myUnit
Every JPA entity inside the package org.generated.project.domain.model and its subpackages will be associated with the
JPA unit myUnit.
This «tag» can be specified at any package level and an upper-level tag can be overridden by a lower-level tag.
Step 5: Entity definition
Although it is possible to write a plain vanilla JPA entity, we are going to leverage a «Domain-Driven Design» (DDD)
approach by using the SeedStack business framework. To do so, we are going
for a very simple DDD-oriented model with a single customer aggregate. It models a Customer with its identity
being the CustomerId value-object.
The CustomerId class:
package org.generated.project.domain.model.customer;
import javax.persistence.Embeddable;
import org.seedstack.business.domain.BaseValueObject;
@Embeddable
public class CustomerId extends BaseValueObject {
private String value;
private CustomerId() {
// A default constructor is needed by JPA but can be kept private
}
public CustomerId(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
The Customer class:
package org.generated.project.domain.model.customer;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import org.seedstack.business.domain.BaseAggregateRoot;
@Entity
public class Customer extends BaseAggregateRoot<CustomerId> {
@EmbeddedId
private CustomerId id;
private String firstName;
private String lastName;
private String address;
private String deliveryAddress;
private String password;
private Customer() {
// A default constructor is needed by JPA but can be kept private
}
public Customer(CustomerId customerId) {
this.id = customerId;
}
@Override
public CustomerId getId() {
return id;
}
}
Note how this model is first and foremost a DDD-oriented model but with JPA annotations mixed in. The JPA annotations simply define the model-to-database mapping of the business concepts.
Step 6: Repository usage
We have defined a DDD aggregate, so we are going to use a repository to store and retrieve it. A repository is a business-oriented encapsulation of the model persistence.
The business framework provides a Repository interface, defining basic repository
features. To obtain a default JPA implementation you must qualify the injection point with the @Jpa
annotation.
Repositories are often used in services or directly in interface-related classes like in REST resources.
Let’s define a service interface:
@Service
public interface SomeService {
void doSomething(CustomerId customerId);
}
And an implementation using a JPA-capable implementation of the Repository interface:
public class SomeServiceImpl implements SomeService {
@Inject
@Jpa
private Repository<Customer, CustomerId> customerRepository;
@Transactional
@JpaUnit("myUnit")
public void doSomething(CustomerId customerId) {
Customer customer = customerRepository.get(customerId)
.orElseThrow(() -> new CustomerNotFoundException(customerId));
// ... do the work
}
}
JPA database access must be done in a transaction. Note how the @Transactional
and @JpaUnit annotations are specified together on the method to mark a JPA transaction
happening on unit myUnit.
Bonus: Custom repository
The base operations provided by the Repository interface are useful but
often not enough, especially if you want to define business-oriented persistence operations for a particular aggregate.
To define a custom repository, an interface is needed:
public interface CustomerRepository extends Repository<Customer, CustomerId> {
Customer findVipCustomers();
}
This custom interface must then be implemented using JPA. The easiest way is to create an implementation class extending
BaseJpaRepository. That way, you’ll only have to implement your custom methods:
public class CustomerJpaRepository extends BaseJpaRepository<Customer, CustomerId>
implements CustomerRepository {
@Override
public List<Customer> findVipCustomers() {
EntityManager entityManager = getEntityManager();
// do work with entityManager
}
}
To inject your custom implementation, use the custom interface at your injection point:
public class SomeServiceImpl implements SomeService {
@Inject
private CustomerRepository customerRepository;
@Transactional
@JpaUnit("myUnit")
public void doSomething(CustomerId customerId) {
List<Customer> vipCustomers = customerRepository.findVipCustomers();
// ... do the work
}
}
Maven
Gradle