JPA With the Business Framework
While you can do plain JPA by injecting EntityManager
anywhere, it is better to define an architectural layer where you
encapsulate persistence-related operations. In the business framework, persistence is confined to
Repositories.
JPA repository
Default JPA repository
The JPA add-on will provide a default JPA repository implementation for every aggregate that does not have a custom one. Two cases may occur:
- You don’t have any custom repository interface an particular aggregate. In that case a JPA implementation of
the base
Repository
interface is provided. - You have a custom repository interface and all its methods are implemented as default methods (no abstract method remaining). In that case a JPA implementation of your interface is provided.
Custom JPA repository
To define a custom JPA repository, first write the custom repository interface:
public interface CustomerRepository extends Repository<Customer, CustomerId> {
Customer findCustomerByName(String name);
}
Then implement the interface in a 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 Customer findCustomerByName(String name) {
EntityManager entityManager = getEntityManager();
// do work with entityManager
}
}
Usage
JPA implementations are always injected by specifying the @Jpa
annotation at the
injection point.
You can inject a JPA repository implementation through the Repository
like this:
public class SomeClass {
@Inject
@Jpa
private Repository<Customer, CustomerId> customerRepository;
}
If you have a custom repository interface, you can inject a JPA implementation of it like this:
public class SomeClass {
@Inject
@Jpa
private CustomerRepository customerRepository;
}
The framework will always choose the most complete implementation available. This means that if you have a custom implementation, it will always be preferred for injection over any default implementation.
Refer to the Repository
interface documentation for details about its
usage.
Sequence generation
The JPA add-on provides implementations of the business framework SequenceGenerator
to enable the use of a database sequence to generate an identity.
Mark the identity field
To mark an entity field as its identity, add the @Identity
annotation:
public class MyAggregate extends BaseAggregateRoot<Long> {
@Identity(generator = SequenceGenerator.class)
private Long id;
}
Choose a generator implementation
While the @Identity
annotation tells the framework that it must generate
an identity, it doesn’t tell it how. This can be done by adding a qualifier annotation on the field:
public class MyAggregate extends BaseAggregateRoot<Long> {
@Identity(generator = SequenceGenerator.class)
@Named("postgreSqlSequence")
private Long id;
}
The following implementations are available:
- PostgreSQL: use the
@Named("postgreSqlSequence")
qualifier. - Oracle: use the
@Named("oracleSequence")
qualifier.
Specify the database sequence name
The generator implementation must know which database sequence to use. To specify it, use the identitySequenceName
tag
in class configuration:
classes:
org:
generated:
project:
domain:
model:
myaggregate:
MyAggregate:
identitySequenceName: MY_SEQUENCE
Usage
Refer to the business framework identity generation documentation for instructions about how to actually use the chosen sequence generator when creating entities.
Example
DDD Aggregate with JPA annotations
This aggregate models a Customer
with its identity being the CustomerId
value-object.
The CustomerId
class:
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:
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;
}
}
Aggregate persistence through a JPA repository
The @Jpa
annotation is used to qualify the repository injection so we get a JPA
implementation of the repository:
@Service
public interface SomeService {
void sendEmail(CustomerId customerId, String content);
}
public class SomeServiceImpl implements SomeService {
@Inject
@Jpa
private Repository<Customer, CustomerId> customerRepository;
@Transactional
@JpaUnit("myUnit")
public void sendEmail(CustomerId customerId, String content) {
Customer customer = customerRepository.get(customerId)
.orElseThrow(() -> new CustomerNotFoundException(customerId));
// ... do the work
}
}